
import Long from 'long';
import * as protobuf from 'protobufjs';
import * as pako from 'pako';

/**
 * 框架功能
 */
var appox = new Appox();
function Appox() {

    /**
     * 获取appox.js版本
     * @returns {string} appox.js版本
     */
    this.getVersion = () => {
        return "2.9.0"; // 新增appoxUtility.configDataEncryption()
    };

    /**
     * 初始化agency
     * @param {string} protoFilePath AgencyTypes.proto文件路径 
     * @returns {Promise} 无参数
     */
    this.initializeAgency = (protoFilePath) => {
        if (this.agencyInitializePromise == null) {
            this.agencyInitializePromise = appoxi7.initializeProtobuf(protoFilePath);
        }
        return this.agencyInitializePromise;
    }

    /**
     * 初始化worker连接
     * @param {string} ip Worker IP
     * @param {number} port Worker端口
     */
    this.initializeWorkerConnection = (ip, port) => {
        if (this.connectionInitialized) return;
        this.connectionInitialized = true;
        appoxi7.connectionMode = appoxEnums.ConnectionMode.Worker;
        appoxi7.ip = ip;
        appoxi7.workerPort = port;
        setInterval(appoxi7.onTimerTick, 10);
    };

    /**
     * 初始化bridge连接
     * @param {string} ip Bridge IP
     * @param {number} userPort Bridge用户端口
     * @param {number} consolePort Bridge console端口
     */
    this.initializeBridgeConnection = (ip, userPort, consolePort) => {
        if (this.connectionInitialized) return;
        this.connectionInitialized = true;
        appoxi7.connectionMode = appoxEnums.ConnectionMode.Bridge;
        appoxi7.ip = ip;
        appoxi7.bridgePort = userPort;
        appoxi7.bridgeConsolePort = consolePort;
        setInterval(appoxi7.onTimerTick, 10);
    };

    /**
     * 断开并稍后重新连接
     */
    this.reconnect = () => {
        appoxi7.reconnect();
    };

    /**
     * 打印调试信息
     * @param {string} message 调试信息
     */
    this.print = (message) => {
        if (message) appoxCallback.onDebugMessage(message.toString(), "General");
    };

    this.agencyInitializePromise = null;
    this.connectionInitialized = false;
}
export { appox };

/**
 * 远程调用函数集
 */
var agencyAsync = new AppoxAgencyAsync();
function AppoxAgencyAsync() {

    /**
     * 添加总线通道引用
     * @param {number} busChannel 总线通道，1~48
     * @returns {Promise} 无参数
     */
    this.addBusMessageReference = async (busChannel) => {
        let inputBytes = appoxi7.intToBytes(busChannel);
        await appoxi7.counterRunCallAgency("AddBusMessageReference", () => inputBytes, false, false);
    };

    /**
     * 添加新的数据层级
     * @param {string} layer 数据层级
     * @returns {Promise} 无参数
     */
    this.addDataLayer = async (layer) => {
        let inputBytes = appoxi7.stringToBytes(layer);
        await appoxi7.defaultSetCallAgency("AddDataLayer", layer, () => inputBytes, false, false);
    };

    /**
     * 添加点云通道引用
     * @param {number} channel 点云数据通道。通道序号从0开始
     * @returns {Promise} 无参数
     */
    this.addPointCloudReference = async (channel) => {
        if (channel >= 0 && channel < appoxi7.pointCloudRefs.length) appoxi7.pointCloudRefs[channel]++;
        let inputBytes = appoxi7.intToBytes(channel);
        await appoxi7.counterRunCallAgency("AddPointCloudReference", () => inputBytes, false, false);
    };

    /**
     * 添加信号引用
     * @param {string} signalID 信号ID
     * @returns {Promise} 无参数
     */
    this.addSignalReference = async (signalID) => {
        let inputBytes = appoxi7.stringToBytes(signalID);
        await appoxi7.counterRunCallAgency("AddSignalReference", () => inputBytes, false, false);
    };
    
    /**
     * 调用原生插件中的函数
     * @param {string} nativeClassID 原生组件ID
     * @param {string} funcID 函数ID
     * @param {Uint8Array} input 函数输入数据
     * @returns {Promise} Uint8Array: 函数输出数据，若未找到相应插件或函数ID无响应则返回null
     */
    this.callNativeFunction = async (nativeClassID, funcID, input) => {
        let inputObj = { nativeClassId: nativeClassID, funcId: funcID, input: input };
        let inputBytes = appoxi7.CallNativeFunctionInput.encode(inputObj).finish();
        let outputBytes = await appoxi7.counterRunCallAgency("CallNativeFunction", () => inputBytes, true, false);
        return outputBytes;
    };

    /**
     * 将时间线上的时间转换为在session中的时间
     * @param {number} timeline 时间线上的时间点，单位秒
     * @returns {Promise} AppoxTimeWithSession: 在session中的时间，若超出范围则返回null
     */
    this.convertTimeIntoSession = async (timeline) => {
        let inputBytes = appoxi7.doubleToBytes(timeline);
        let outputBytes = await appoxi7.defaultGetCallAgency("ConvertTimeIntoSession", timeline, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.TimeWithSession.decode(outputBytes);
        let dstSession = new AppoxSessionIdentifier(output.session);
        if (!dstSession.isValid) return null;
        return new AppoxTimeWithSession(dstSession, output.time);
    };

    /**
     * 移除数据层级，并删除所有文件至回收站
     * @param {string} layer 数据层级
     * @returns {Promise} 无参数
     */
    this.deleteDataLayer = async (layer) => {
        let inputBytes = appoxi7.stringToBytes(layer);
        await appoxi7.defaultSetCallAgency("DeleteDataLayer", layer, () => inputBytes, false, true);
    };

    /**
     * 接收所有从原生插件发来的新数据
     * @param {string} nativeClassID 原生组件ID
     * @param {string} dataID 数据ID，不可为null
     * @returns {Promise} Uint8Array数组: 所有新数据
     */
    this.dequeueDataFromNative = async (nativeClassID, dataID) => {
        let input = { item1: nativeClassID, item2: dataID };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let outputBytes = await appoxi7.counterRunCallAgency("DequeueDataFromNative", () => inputBytes, false, false);
        if (outputBytes == null) return null;
        let output = appoxi7.DequeueDataFromNativeOutput.decode(outputBytes);
        return output.dataArray;
    };

    /**
     * 禁用所有组件配置
     * @returns {Promise} 无参数
     */
    this.disableAllConfigs = async () => {
        await appoxi7.defaultSetCallAgency("DisableAllConfigs", null, null, false, false);
    };

    /**
     * 禁用所有插件（除当前流程插件外）
     * @returns {Promise} 无参数
     */
    this.disableAllPlugins = async () => {
        await appoxi7.defaultSetCallAgency("DisableAllPlugins", null, null, false, false);
    };

    /**
     * 禁用数据处理/原生/设备组件
     * @param {string} controller 控制者名称
     * @param {string} classID 组件的类别ID
     * @returns {Promise} 无参数
     */
    this.disableModule = async (controller, classID) => {
        let input = { item1: controller, item2: classID };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        await appoxi7.defaultSetCallAgency("DisableModule", classID, () => inputBytes, false, false);
    };

    /**
     * 禁用插件
     * @param {string} packID 插件包ID
     * @returns {Promise} 无参数
     */
    this.disablePlugin = async (packID) => {
        let inputBytes = appoxi7.stringToBytes(packID);
        await appoxi7.defaultSetCallAgency("DisablePlugin", packID, () => inputBytes, false, false);
    };

    /**
     * 启用数据处理/原生/设备组件
     * @param {string} controller 控制者名称
     * @param {string} classID 组件的类别ID
     * @returns {Promise} 无参数
     */
    this.enableModule = async (controller, classID) => {
        let input = { item1: controller, item2: classID };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        await appoxi7.defaultSetCallAgency("EnableModule", classID, () => inputBytes, false, false);
    };

    /**
     * 启用插件（需要重启应用程序后生效）
     * @param {string} packID 插件包ID
     * @returns {Promise} 无参数
     */
    this.enablePlugin = async (packID) => {
        let inputBytes = appoxi7.stringToBytes(packID);
        await appoxi7.defaultSetCallAgency("EnablePlugin", packID, () => inputBytes, false, false);
    };

    /**
     * 发送数据至原生插件
     * @param {string} nativeClassID 原生组件ID
     * @param {string} dataID 数据ID，不可为null
     * @param {Uint8Array} data 数据，不可为null
     * @returns {Promise} 无参数
     */
    this.enqueueDataToNative = async (nativeClassID, dataID, data) => {
        let input = { nativeClassId: nativeClassID, dataId: dataID, data: data };
        let inputBytes = appoxi7.EnqueueDataToNativeInput.encode(input).finish();
        await appoxi7.counterRunCallAgency("EnqueueDataToNative", () => inputBytes, true, false);
    };

    /**
     * 按信号值生成用于发送的报文
     * @param {string} messageID 总线报文ID
     * @param {Object} signalValues 信号值表，键为信号名称(string)，值为信号值(number)，可先通过 getSignalNamesOfBusMessage 获取信号名称列表
     * @param {number} defaultValue 信号值表未提供的信号使用的默认值
     * @param {number} interval 报文发送周期，单位毫秒（至少为10），若设为null则只发送一次
     * @returns {Promise} AppoxBusMessage: 用于发送的报文
     */
    this.generateBusMessage = async (messageID, signalValues, defaultValue, interval) => {
        let input = { messageId: messageID, signalValues: signalValues, defaultValue: defaultValue, interval: interval };
        let inputBytes = appoxi7.GenerateBusMessageInput.encode(input).finish();
        let outputBytes = await appoxi7.counterRunCallAgency("GenerateBusMessage", () => inputBytes, false, false);
        if (outputBytes == null) return null;
        let output = appoxi7.BusMessage.decode(outputBytes);
        return new AppoxBusMessage(output.channel, output.id, output.data, output.millisecondInterval);
    };

    /**
     * 获取已配置为客机同步的所有ID
     * @returns {Promise} string数组: 已配置为客机同步的所有ID列表
     */
    this.getAllChannelGuestSyncKeys = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAllChannelGuestSyncKeys", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取所有正在监控有无数据的通道ID
     * @returns {Promise} string数组: 正在监控有无数据的通道ID列表
     */
    this.getAllChannelMonitoringKeys = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAllChannelMonitoringKeys", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取所有正在监控数据与授时服务器同步的监控ID
     * @returns {Promise} string数组: 正在监控数据与授时服务器同步的通道ID列表
     */
    this.getAllChannelServerSyncMonitoringKeys = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAllChannelServerSyncMonitoringKeys", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取所有设备状态
     * @returns {Promise} Object: 设备状态表，键为设备的原生类型ID或组件ID，值为AppoxDeviceStatusDetail
     */
    this.getAllDeviceStatus = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAllDeviceStatus", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetAllDeviceStatusOutput.decode(outputBytes);
        let result = {};
        let gds = appoxEnums.GeneralDeviceStatus;
        for (let key in output.detailMap) {
            let src = output.detailMap[key];
            let dstStatus = appoxi7.stringToEnum(gds, src.status, gds.None);
            result[key] = new AppoxDeviceStatusDetail(dstStatus, src.description);
        }
        return result;
    };

    /**
     * 获取所有原始通道延迟配置
     * @returns {Promise} Object: 原始通道延迟配置表，键为通道ID，值为延迟配置，单位毫秒
     */
    this.getAllRawChannelDelayConfigs = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAllRawChannelDelayConfigs", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringDoubleMap.decode(outputBytes);
        return output.numberMap;
    };

    /**
     * 获取应用程序基于的图形界面框架
     * @returns {Promise} ApplicationGUI: 图形界面框架
     */
    this.getAppGUI = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAppGUI", null, null);
        let ag = appoxEnums.ApplicationGUI;
        return appoxi7.bytesToEnum(ag, outputBytes, ag.Unknown);
    };

    /**
     * 获取应用程序ID
     * @returns {Promise} string: 应用程序ID
     */
    this.getAppID = async () => {
        return appoxi7.bytesToString(await appoxi7.defaultGetCallAgency("GetAppID", null, null), null);
    };

    /**
     * 获取应用程序显示语言
     * @returns {Promise} Language: 应用程序的显示语言
     */
    this.getAppLanguage = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAppLanguage", null, null);
        let l = appoxEnums.Language;
        return appoxi7.bytesToEnum(l, outputBytes, l.Invalid);
    };

    /**
     * 获取应用程序当前的运行模式
     * @returns {Promise} ApplicationMode: 应用程序运行模式
     */
    this.getAppMode = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAppMode", null, null);
        let am = appoxEnums.ApplicationMode;
        return appoxi7.bytesToEnum(am, outputBytes, am.Unknown);
    };

    /**
     * 获取应用程序当前的运行状态
     * @returns {Promise} ApplicationStatus: 应用程序运行状态
     */
    this.getAppStatus = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAppStatus", null, null);
        let as = appoxEnums.ApplicationStatus;
        return appoxi7.bytesToEnum(as, outputBytes, as.Unknown);
    };

    /**
     * 获取音频数据通道延迟配置
     * @returns {Promise} number: 延迟配置，单位毫秒
     */
    this.getAudioChannelDelayConfig = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAudioChannelDelayConfig", null, null);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取音频数据通道状态
     * @param {number} toleranceMillisecond 无数据的容忍时长
     * @returns {Promise} Array: [是否有数据, 每帧数据之间的时间间隔曲线(秒), 每帧数据的延迟曲线(秒)]
     */
    this.getAudioChannelStatus = async (toleranceMillisecond) => {
        let input = { value: toleranceMillisecond };
        let inputBytes = appoxi7.OptionalUInt.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAudioChannelStatus", toleranceMillisecond, () => inputBytes);
        if (outputBytes == null) return [ false, null, null ];
        let output = appoxi7.GetChannelStatusOutput.decode(outputBytes);
        return [ output.item1, output.item2, output.item3 ];
    };

    /**
     * 获取所有已注册的音频驱动信息
     * @returns {Promise} AppoxAudioDriverInfo数组: 已注册的音频驱动信息列表，若未注册任何有效驱动则返回null
     */
    this.getAudioDrivers = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAudioDrivers", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetAudioDriversOutput.decode(outputBytes);
        let result = [];
        output.infos.forEach((srcInfo) => {
            result.push(new AppoxAudioDriverInfo(srcInfo.driverId, srcInfo.driverName));
        });
        return result;
    };

    /**
     * 获取指定驱动下的音频采集设备信息列表
     * @param {string} driverID 音频驱动ID
     * @returns {Promise} AppoxAudioDeviceInfo数组: 音频采集设备信息列表，若无该驱动或驱动下无采集设备则返回null
     */
    this.getAudioRecordDevices = async (driverID) => {
        let inputBytes = appoxi7.stringToBytes(driverID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAudioRecordDevices", driverID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetAudioDevicesOutput.decode(outputBytes);
        let result = [];
        output.infos.forEach((srcInfo) => {
            result.push(new AppoxAudioDeviceInfo(srcInfo.deviceId, srcInfo.deviceName));
        });
        return result;
    };

    /**
     * 获取指定驱动下的音频回放设备信息列表
     * @param {string} driverID 音频驱动ID
     * @returns {Promise} AppoxAudioDeviceInfo数组: 音频回放设备信息列表，若无该驱动或驱动下无回放设备则返回null
     */
    this.getAudioReplayDevices = async (driverID) => {
        let inputBytes = appoxi7.stringToBytes(driverID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAudioReplayDevices", driverID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetAudioDevicesOutput.decode(outputBytes);
        let result = [];
        output.infos.forEach((srcInfo) => {
            result.push(new AppoxAudioDeviceInfo(srcInfo.deviceId, srcInfo.deviceName));
        });
        return result;
    };

    /**
     * 获取所有有效的总线通道
     * @returns {Promise} number数组: 有效的总线通道列表，值为1~48
     */
    this.getAvailableBusChannels = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAvailableBusChannels", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.IntArray.decode(outputBytes);
        return output.intArray;
    };

    /**
     * 获取所有有效的原始数据通道
     * @returns {Promise} string数组: 有效的原始数据通道列表，值为通道ID
     */
    this.getAvailableRawChannels = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAvailableRawChannels", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取所有有效的样本数据通道
     * @returns {Promise} string数组: 有效的样本数据通道列表，值为通道ID
     */
    this.getAvailableSampleChannels = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAvailableSampleChannels", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取所有有效的视频通道
     * @returns {Promise} number数组: 有效的视频通道列表，值为0~23
     */
    this.getAvailableVideoChannels = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetAvailableVideoChannels", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.IntArray.decode(outputBytes);
        return output.intArray;
    };

    /**
     * 获取应用程序当前的数据缓存范围
     * @returns {Promise} AppoxBufferRange: 数据缓存范围
     */
    this.getBufferRange = async () => {
        let outputBytes = await appoxi7.counterRunCallAgency("GetBufferRange", null, false, true);
        if (outputBytes == null) return null;
        let output = appoxi7.TwoNumber.decode(outputBytes);
        return new AppoxBufferRange(output.item1, output.item2);
    };

    /**
     * 获取总线数据通道延迟配置
     * @param {number} busChannel 总线通道，1~48
     * @returns {Promise} number: 延迟配置，单位毫秒
     */
    this.getBusChannelDelayConfig = async (busChannel) => {
        let inputBytes = appoxi7.intToBytes(busChannel);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusChannelDelayConfig", busChannel, () => inputBytes);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取指定session的所有总线通道的信息
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} AppoxBusChannelInfo数组: 该session的所有总线通道的信息，若不存在则返回null
     */
    this.getBusChannelsInfo = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusChannelsInfo", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetBusChannelsInfoOutput.decode(outputBytes);
        let result = [];
        let bct = appoxEnums.BusChannelType;
        output.infos.forEach((srcInfo) => {
            result.push(new AppoxBusChannelInfo(appoxi7.stringToEnum(bct, srcInfo.type, bct.None)));
        })
        return result;
    };

    /**
     * 获取总线数据通道状态
     * @param {number} busChannel 总线通道，1~48
     * @param {number} toleranceMillisecond 无数据的容忍时长
     * @returns {Promise} boolean: 是否有数据
     */
    this.getBusChannelStatus = async (busChannel, toleranceMillisecond) => {
        let input = { channel: busChannel, toleranceMillisecond: toleranceMillisecond };
        let inputBytes = appoxi7.GetChannelStatusInput.encode(input).finish();
        let id = busChannel + "." + toleranceMillisecond;
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusChannelStatus", id, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取总线设备列表
     * @returns {Promise} AppoxBusDeviceInfo数组: 总线设备信息列表
     */
    this.getBusDevices = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusDevices", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetBusDevicesOutput.decode(outputBytes);
        let result = [];
        let bct = appoxEnums.BusChannelType;
        output.infoMap.forEach((pair) => {
            let srcID = pair.key;
            let srcInfo = pair.value;
            let dstID = new AppoxBusDeviceID(srcID.type, appoxi7.longToBigInt(srcID.serial, 0), srcID.index);
            let dstTypes = [];
            srcInfo.supportedTypes.forEach((srcType) => {
                let dstType = appoxi7.stringToEnum(bct, srcType, bct.None);
                if (dstType != bct.None) dstTypes.push(dstType);
            });
            let dstInfo = new AppoxBusDeviceInfo(dstID, dstTypes, srcInfo.description);
            result.push(dstInfo);
        });
        return result;
    };

    /**
     * 获取指定通道收到的指定ID报文的帧率
     * @param {number} busChannel 总线通道，1~48
     * @param {number} localID 通道内的报文ID
     * @returns {Promise} number: 每秒帧率，0表示无效
     */
    this.getBusMessageFPS = async (busChannel, localID) => {
        let input = { item1: busChannel, item2: localID };
        let inputBytes = appoxi7.IntAndUInt.encode(input).finish();
        let id = Long.fromBits(localID, busChannel, true);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusMessageFPS", id, () => inputBytes);
        return appoxi7.bytesToFloat(outputBytes, 0);
    };

    /**
     * 获取指定通道上指定ID报文信息
     * @param {number} busChannel 总线通道，1~48
     * @param {number} localID 通道内的报文ID
     * @returns {Promise} AppoxBusMessageInfo: 总线报文信息，无信息则返回null
     */
    this.getBusMessageInfoByLocalID = async (busChannel, localID) => {
        let input = { item1: busChannel, item2: localID };
        let inputBytes = appoxi7.IntAndUInt.encode(input).finish();
        let id = Long.fromBits(localID, busChannel, true);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusMessageInfoByLocalID", id, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.BusMessageInfo.decode(outputBytes);
        return new AppoxBusMessageInfo(output.messageId, output.messageName, output.localId, output.byteLength, output.protocolFileId);
    };

    /**
     * 获取指定ID的总线报文的信息
     * @param {string} busMessageID 总线报文ID
     * @returns {Promise} AppoxBusMessageInfo: 总线报文的信息，报文不存在则返回null
     */
    this.getBusMessageInfo = async (busMessageID) => {
        let inputBytes = appoxi7.stringToBytes(busMessageID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusMessageInfo", busMessageID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.BusMessageInfo.decode(outputBytes);
        return new AppoxBusMessageInfo(output.messageId, output.messageName, output.localId, output.byteLength, output.protocolFileId);
    };

    /**
     * 获取指定总线通道的负载百分比
     * @param {number} busChannel 总线通道，1~48
     * @returns {Promise} number: 总线通道的负载百分比，若无效则返回null
     */
    this.getBusPayloadPercentage = async (busChannel) => {
        let inputBytes = appoxi7.intToBytes(busChannel);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusPayloadPercentage", busChannel, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.OptionalDouble.decode(outputBytes);
        return output.value;
    };

    /**
     * 获取总线协议文件当前配置于哪个通道
     * @param {string} protocolName 总线协议名称（多通道的情况下包括通道名）
     * @returns {Promise} number: 总线通道（1~48），若未配置则返回null
     */
    this.getBusProtocolFileChannel = async (protocolName) => {
        let inputBytes = appoxi7.stringToBytes(protocolName);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusProtocolFileChannel", protocolName, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.OptionalInt.decode(outputBytes);
        return output.value;
    };

    /**
     * 获取总线协议文件ID列表
     * @returns {Promise} AppoxBusProtocolFileID数组: 总线协议文件ID列表
     */
    this.getBusProtocolFileIDList = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusProtocolFileIDList", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetBusProtocolFileIDListOutput.decode(outputBytes);
        let result = [];
        output.list.forEach((id) => {
            result.push(new AppoxBusProtocolFileID(id.fileName, id.md5));
        });
        return result;
    };

    /**
     * 获取总线协议对应文件的状态
     * @param {AppoxBusProtocolFileID} fileID 总线协议文件ID
     * @returns {Promise} BusProtocolFileState: 文件状态
     */
    this.getBusProtocolFileState = async (fileID) => {
        let bpfs = appoxEnums.BusProtocolFileState;
        if (!(fileID instanceof AppoxBusProtocolFileID)) return bpfs.Unknown;
        let inputBytes = appoxi7.BusProtocolFileID.encode(fileID).finish();
        let id = fileID.fileName + ":" + fileID.md5;
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusProtocolFileState", id, () => inputBytes);
        return appoxi7.bytesToEnum(bpfs, outputBytes, bpfs.Unknown);
    };

    /**
     * 获取指定ID的总线信号的信息
     * @param {string} busSignalID 总线信号ID
     * @returns {Promise} AppoxBusSignalInfo: 总线信号的信息，信号不存在或信号非总线信号则返回null
     */
    this.getBusSignalInfo = async (busSignalID) => {
        let inputBytes = appoxi7.stringToBytes(busSignalID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetBusSignalInfo", busSignalID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.BusSignalInfo.decode(outputBytes);
        let dstEnums = {};
        for (let key in output.enums) {
            dstEnums[appoxi7.stringOr8CharsHashToLong(key, true).toString()] = output.enums[key];
        }
        return new AppoxBusSignalInfo(output.signalId, output.signalName, output.unit, dstEnums);
    };

    /**
     * 获取数据通道别名
     * @param {string} channelID 数据通道关键字，格式为"协议名@通道序号"
     * @returns {Promise} string: 数据通道别名，若未找到返回null
     */
    this.getChannelAliasName = async (channelID) => {
        let inputBytes = appoxi7.stringToBytes(channelID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChannelAliasName", channelID, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取数据通道别名表
     * @returns {Promise} Object: 数据所有通道的别名表，键为通道ID，值为通道别名
     */
    this.getChannelAliasTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChannelAliasTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringStringMap.decode(outputBytes);
        return output.stringMap;
    };

    /**
     * 获取指定通道是否已配置为客机同步
     * @param {string} id 客机同步ID，如bus.1, video.0, xxx.yyy等
     * @returns {Promise} boolean: 是否已配置为客机同步
     */
    this.getChannelGuestSyncFlag = async (id) => {
        let inputBytes = appoxi7.stringToBytes(id);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChannelGuestSyncFlag", id, () => inputBytes);
        if (outputBytes == null) return null;
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取数据通道上最近的若干帧时间戳
     * @param {string} channelID 数据通道ID，格式为"协议名@通道序号"
     * @returns {Promise} AppoxTimestamp数组: 指定数据通道上最近的若干帧时间戳，若该通道未找到或最近无数据则返回null
     */
    this.getChannelLatestTimestamps = async (channelID) => {
        let inputBytes = appoxi7.stringToBytes(channelID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChannelLatestTimestamps", channelID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetChannelLatestTimestampsOutput.decode(outputBytes);
        let result = [];
        let tos = appoxEnums.TimeOffsetSync;
        output.timestamps.forEach((srcT) => {
            let dstT = appoxi7.convToAppoxTimestamp(srcT);
            if (dstT != null) result.push(dstT);
        });
        return result;
    };

    /**
     * 获取是否监控指定通道有无数据
     * @param {string} id 监控ID，如：bus@1, video@0, audio等
     * @returns {Promise} boolean: 是否监控有无数据
     */
    this.getChannelMonitoringFlag = async (id) => {
        let inputBytes = appoxi7.stringToBytes(id);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChannelMonitoringFlag", id, () => inputBytes);
        if (outputBytes == null) return null;
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取是否监控指定通道数据与授时服务器同步
     * @param {string} id 监控ID，如bus@1, video@0, sample@xxx-v1@0等
     * @returns {Promise} boolean: 是否监控指定通道数据与授时服务器同步
     */
    this.getChannelServerSyncMonitoringFlag = async (id) => {
        let inputBytes = appoxi7.stringToBytes(id);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChannelServerSyncMonitoringFlag", id, () => inputBytes);
        if (outputBytes == null) return null;
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取所有通道的数据状态
     * @param {number} tolerance 可容忍最近多少毫秒（现实时间）无数据
     * @returns {Promise} Object: 各通道的数据状态，key为通道ID，value为boolean
     */
    this.getChannelStatusTable = async (tolerance) => {
        let input = { value: tolerance };
        let inputBytes = appoxi7.OptionalUInt.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChannelStatusTable", tolerance, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringBoolMap.decode(outputBytes);
        return output.boolMap;
    };

    /**
     * 获取所有通道的时间同步状态
     * @returns {Promise} Object: 各通道的时间同步状态，key为通道ID，value为TimeOffsetSync
     */
    this.getChannelSyncTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChannelSyncTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringStringMap.decode(outputBytes);
        let result = {};
        let tos = appoxEnums.TimeOffsetSync;
        for (let key in output.stringMap) {
            let srcSync = output.stringMap[key];
            let dstSync = appoxi7.stringToEnum(tos, srcSync, null);
            if (dstSync != null) result[key] = dstSync;
        }
        return result;
    };

    /**
     * 获取各子设备的设备状态
     * @param {string} id 设备原生类型ID，或设备组件ID
     * @returns {Promise} GeneralDeviceStatus数组: 各子设备的设备状态
     */
    this.getChildDeviceStatus = async (id) => {
        let inputBytes = appoxi7.stringToBytes(id);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetChildDeviceStatus", id, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        let result = [];
        let gds = appoxEnums.GeneralDeviceStatus;
        output.stringArray.forEach((status) => {
            result.push(appoxi7.stringToEnum(gds, status, gds.None));
        });
        return result;
    };

    /**
     * 获取控制台组件信息
     * @param {string} consoleClassID 控制台组件ID
     * @returns {Promise} AppoxConsoleClassInfo: 控制台组件信息，若未找到返回null
     */
    this.getConsoleClassInfo = async (consoleClassID) => {
        let inputBytes = appoxi7.stringToBytes(consoleClassID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetConsoleClassInfo", consoleClassID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.ConsoleClassInfo.decode(outputBytes);
        return new AppoxConsoleClassInfo(output.ownerPluginId, output.id, output.title);
    };

    /**
     * 获取控制台组件信息表
     * @returns {Promise} Object: 控制台组件信息表，键为组件ID，值为AppoxConsoleClassInfo
     */
    this.getConsoleClassTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetConsoleClassTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetConsoleClassTableOutput.decode(outputBytes);
        let result = {};
        for (let key in output.infoMap) {
            let srcInfo = output.infoMap[key];
            result[key] = new AppoxConsoleClassInfo(srcInfo.ownerPluginId, srcInfo.id, srcInfo.title);
        }
        return result;
    };

    /**
     * 获取控制台相关组件配置状态
     * @param {string} consoleClassID 控制台组件ID
     * @returns {Promise} Array: [配置状态, 子配置状态数组]
     */
    this.getConsoleRelatedModulesConfigStatus = async (consoleClassID) => {
        let inputBytes = appoxi7.stringToBytes(consoleClassID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetConsoleRelatedModulesConfigStatus", consoleClassID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetRelatedModulesConfigStatusOutput.decode(outputBytes);
        let cs = appoxEnums.ConfigStatus;
        let dstChildren = [];
        output.item2.forEach((srcChild) => {
            dstChildren.push(appoxi7.stringToEnum(cs, srcChild, cs.Disabled));
        });
        return [ appoxi7.stringToEnum(cs, output.item1, cs.Disabled), dstChildren ];
    };

    /**
     * 获取框架实现的ID
     * @returns {Promise} string: 框架实现的ID
     */
    this.getCoreImplementation = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCoreImplementation", null, null);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取主机当前的CPU计数
     * @returns {Promise} bigint: 当前的CPU计数
     */
    this.getCPUTick = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCPUTick", null, null);
        return appoxi7.bytesToBigInt(outputBytes, 0);
    };

    /**
     * 获取主机上每秒增加的CPU计数
     * @returns {Promise} bigint: 每秒增加的CPU计数
     */
    this.getCPUTicksPerSecond = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCPUTicksPerSecond", null, null);
        return appoxi7.bytesToBigInt(outputBytes, 0);
    };

    /**
     * 获取CPU时间（从开机起算的时间）
     * @returns {Promise} number: CPU时间，单位秒，返回0表示无效
     */
    this.getCPUTime = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCPUTime", null, null);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取session的CPU时间模型
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} AppoxCPUTimeModel: CPU时间模型
     */
    this.getCPUTimeModel = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCPUTimeModel", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.CPUTimeModel.decode(outputBytes);
        return new AppoxCPUTimeModel(appoxi7.longToBigInt(output.startCpuTick, 0), appoxi7.longToBigInt(output.cpuTicksPerSecond, 0));
    };

    /**
     * 获取当前输入数据的generation ID
     * @returns {Promise} string: 当前输入数据的generation ID，空表示输入数据为原始数据
     */
    this.getCurrentDataGeneration = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCurrentDataGeneration", null, null);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取当前的数据层级
     * @returns {Promise} string: 数据层级，其中null表示所有层级，'.'表示根路径下的session，'..'表示根路径即session
     */
    this.getCurrentDataLayer = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCurrentDataLayer", null, null);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取采集模式(在线/远程)下正在预览或采集的session
     * @returns {Promise} AppoxSessionIdentifier: 采集模式(在线/远程)下正在预览或采集的session，若非采集模式则返回null
     */
    this.getCurrentOnlineSession = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCurrentOnlineSession", null, null);
        return appoxi7.bytesToSession(outputBytes);
    };

    /**
     * 获取当前session的GUID
     * @returns {Promise} string: 当前session的GUID，若未运行则返回null
     */
    this.getCurrentSessionGUID = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetCurrentSessionGUID", null, null);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取所有有效的数据层级
     * @returns {Promise} string数组: 数据层级列表，其中'.'表示根路径下的session，'..'表示根路径即session
     */
    this.getDataLayers = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetDataLayers", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取数据引用计数信息
     * @returns {Promise} AppoxDataReferenceInfo: 数据引用计数信息
     */
    this.getDataReferenceInfo = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetDataReferenceInfo", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.DataReferenceInfo.decode(outputBytes);
        return new AppoxDataReferenceInfo(output.alwaysTransferBusMessage, output.busMessageReferences, output.alwaysTransferPointCloud, output.pointCloudReferences, output.alwaysTransferProcessorVideo, output.processorVideoReferences, output.alwaysTransferSignal, output.signalReferences, output.subscriberReferences);
    };

    /**
     * 获取设备组件信息表
     * @returns {Promise} Object: 设备组件信息表，键为组件ID，值为AppoxDeviceClassInfo
     */
    this.getDeviceClassTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetDeviceClassTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetDeviceClassTableOutput.decode(outputBytes);
        let result = {};
        for (let key in output.infoMap) {
            let srcInfo = output.infoMap[key];
            result[key] = new AppoxDeviceClassInfo(srcInfo.ownerPluginId, srcInfo.id, srcInfo.title);
        }
        return result;
    };

    /**
     * 获取设备状态
     * @param {string} id 设备原生类型ID，或设备组件ID
     * @returns {Promise} GeneralDeviceStatus: 返回设备状态
     */
    this.getDeviceStatus = async (id) => {
        let inputBytes = appoxi7.stringToBytes(id);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetDeviceStatus", id, () => inputBytes);
        let gds = appoxEnums.GeneralDeviceStatus;
        return appoxi7.bytesToEnum(gds, outputBytes, gds.None);
    };

    /**
     * 获取对话框相关组件配置状态
     * @param {string} dialogClassID 对话框组件ID
     * @param {string} transformID 分化ID
     * @returns {Promise} Array: [配置状态, 子配置状态数组]
     */
    this.getDialogRelatedModulesConfigStatus = async (dialogClassID, transformID) => {
        let input = { item1: dialogClassID, item2: transformID };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let id = dialogClassID + ":" + (transformID == null ? "" : transformID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetDialogRelatedModulesConfigStatus", id, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetRelatedModulesConfigStatusOutput.decode(outputBytes);
        let cs = appoxEnums.ConfigStatus;
        let dstChildren = [];
        output.item2.forEach((srcChild) => {
            dstChildren.push(appoxi7.stringToEnum(cs, srcChild, cs.Disabled));
        });
        return [ appoxi7.stringToEnum(cs, output.item1, cs.Disabled), dstChildren ];
    };

    /**
     * 返回事件对象列表
     * @returns {Promise} string数组: 事件对象列表
     */
    this.getEventHandles = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetEventHandles", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 返回指定事件对象的完整信息
     * @param {string} eventHandle 事件对象
     * @returns {Promise} AppoxEventInfo: 事件完整信息，null表示事件对象无效或信息不完整
     */
    this.getEventInfo = async (eventHandle) => {
        let inputBytes = appoxi7.stringToBytes(eventHandle);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetEventInfo", eventHandle, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.EventInfo.decode(outputBytes);
        let srcData = output.data;
        let dstSession = new AppoxSessionIdentifier(srcData.session);
        if (!dstSession.isValid) return null;
        let dstData = new AppoxEventData(srcData.name, dstSession, srcData.offset, appoxi7.stringToDate(srcData.timestampLocal), srcData.configId, srcData.description, srcData.eventHandle);
        let esrs = appoxEnums.EventSessionRecordStatus;
        return new AppoxEventInfo(dstData, output.comment, appoxi7.stringToEnum(esrs, output.sessionRecordStatus, esrs.Disabled));
    };

    /**
     * 获取事件快照内容
     * @param {string} eventHandle 事件对象
     * @param {EventSnapshotContentType} contentType 内容类型
     * @returns {Promise} Uint8Array: 事件快照内容，若失败则返回null
     */
    this.getEventSnapshotContent = async (eventHandle, contentType) => {
        let input = { item1: eventHandle, item2: contentType };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetEventSnapshotContent", eventHandle, () => inputBytes);
        return outputBytes;
    };

    /**
     * 获取事件类型名称列表（包括未启用的）
     * @returns {Promise} string数组: 事件类型名称列表
     */
    this.getEventTypeNames = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetEventTypeNames", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取当前数据层级下筛选后的所有session
     * @returns {Promise} AppoxSessionIdentifier数组: Session ID列表
     */
    this.getFilteredSessionList = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetFilteredSessionList", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        let result = [];
        output.stringArray.forEach((str) => {
            let dstSession = new AppoxSessionIdentifier(str);
            if (dstSession.isValid) result.push(dstSession);
        });
        return result;
    };

    /**
     * 获取当前层级下筛选后的所有session的时长总长
     * @returns {Promise} number: 筛选后的所有session的时长总长
     */
    this.getFilteredSessionListTotalLength = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetFilteredSessionListTotalLength", null, null);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取当前层级下某个generation下所有处理完毕的session
     * @param {string} generation 目标generation
     * @returns {Promise} AppoxSessionIdentifier数组: 处理完毕的session ID列表
     */
    this.getFinishedSessions = async (generation) => {
        let inputBytes = appoxi7.stringToBytes(generation);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetFinishedSessions", generation, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        let result = [];
        output.stringArray.forEach((str) => {
            let dstSession = new AppoxSessionIdentifier(str);
            if (dstSession.isValid) result.push(dstSession);
        });
        return result;
    };

    /**
     * 获取框架软件使用的第三方软件版权声明
     * @returns {Promise} Object: 键为标题，值为版权声明
     */
    this.getFrameworkThirdPartyNotices = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetFrameworkThirdPartyNotices", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringStringMap.decode(outputBytes);
        return output.stringMap;
    };

    /**
     * 获取generation列表
     * @returns {Promise} string数组: generation列表
     */
    this.getGenerationList = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGenerationList", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取generation处理状态
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {string} generation 目标generation
     * @returns {Promise} GenerationProcessStatus: generation处理状态
     */
    this.getGenerationProcessStatus = async (session, generation) => {
        if (session == null || !session.isValid) return null;
        let input = { item1: session.toString(), item2: generation };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let id = session.toString() + "." + (generation == null ? "" : generation);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGenerationProcessStatus", id, () => inputBytes);
        let enumString = appoxi7.bytesToString(outputBytes, null);
        if (enumString == null) return null;
        let gps = appoxEnums.GenerationProcessStatus;
        return appoxi7.stringToEnum(gps, enumString, gps.Unknown);
    };

    /**
     * 获取generation下的所有session
     * @param {string} generationID generation ID
     * @returns {Promise} AppoxSessionIdentifier数组: session ID列表
     */
    this.getGenerationSessions = async (generationID) => {
        let inputBytes = appoxi7.stringToBytes(generationID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGenerationSessions", generationID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        let result = [];
        output.stringArray.forEach((str) => {
            let dstSession = new AppoxSessionIdentifier(str);
            if (dstSession.isValid) result.push(dstSession);
        });
        return result;
    };

    /**
     * 获取全局参数
     * @param {string} key 参数关键字
     * @param {string} defaultValue 默认值
     * @returns {Promise} string: 参数值
     */
    this.getGlobalParameter = async (key, defaultValue) => {
        let input = { item1: key, item2: defaultValue };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGlobalParameter", key, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, defaultValue);
    };

    /**
     * 获取全局参数关键字列表
     * @returns {Promise} string数组: 全局参数关键字列表
     */
    this.getGlobalParameterKeys = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGlobalParameterKeys", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取全局变量
     * @param {string} key 变量关键字
     * @param {string} defaultValue 默认值
     * @returns {Promise} string: 变量值
     */
    this.getGlobalVariable = async (key, defaultValue) => {
        let input = { item1: key, item2: defaultValue };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGlobalVariable", key, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, defaultValue);
    };

    /**
     * 获取全局变量关键字列表
     * @returns {Promise} string数组: 全局变量关键字列表
     */
    this.getGlobalVariableKeys = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGlobalVariableKeys", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取GNSS的POSIX时间模型
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} AppoxPosixTimeModel: POSIX时间模型
     */
    this.getGNSSPosixTimeModel = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGNSSPosixTimeModel", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.PosixTimeModel.decode(outputBytes);
        return new AppoxPosixTimeModel(appoxi7.longToBigInt(output.startPosix, 0), output.timeRatio);
    };

    /**
     * 获取GPU解码器测试结果
     * @returns {Promise} AppoxGPUDecoderTestResults: GPU解码器测试结果
     */
    this.getGPUDecoderTestResults = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGPUDecoderTestResults", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GPUDecoderTestResults.decode(outputBytes);
        let testResults = {};
        for (let key in output.testResults) {
            let srcResult = output.testResults[key];
            testResults[key] = appoxi7.longToBigInt(srcResult.value, null);
        }
        return new AppoxGPUDecoderTestResults(output.testDataAvailable, testResults);
    };

    /**
     * 获取图表对象
     * @param {AppoxSessionIdentifier} session 想要获取图表的session ID
     * @param {number} graphID 图表报告ID，通过 AppoxGraphDefinition.GetID 获取
     * @returns {Promise} AppoxGraphData: 图表对象(可以为AppoxSingleValueData, AppoxScatterPointsData等特化类型对象)，若不存在或不属于当前层级则返回null
     */
    this.getGraphData = async (session, graphID) => {
        if (session == null || !session.isValid) return null;
        let input = { session: session.toString(), graphId: graphID };
        let inputBytes = appoxi7.GetGraphDataInput.encode(input).finish();
        let id = session.toString() + "." + graphID;
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGraphData", id, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GraphData.decode(outputBytes);
        let srcDef = output.definition;
        let gt = appoxEnums.GraphType;
        let dstType = appoxi7.stringToEnum(gt, srcDef.type, gt.Invalid);
        if (dstType == gt.Invalid) return null;
        let dstDef = new AppoxGraphDefinition(dstType, srcDef.config, srcDef.mainTitle, srcDef.columnTitles);
        let source = new AppoxGraphData(output.id, dstDef, output.params, output.dataDim0, output.dataDim1, output.data);
        switch (dstType) {
            case gt.SingleValue:
                return new AppoxSingleValueData(source);
            case gt.ScatterPoints:
                return new AppoxScatterPointsData(source);
            case gt.HistAndLine:
                return new AppoxHistAndLineData(source);
            case gt.MatrixTable:
                return new AppoxMatrixTableData(source);
            case gt.LabelTable:
                return new AppoxLabelTableData(source);
            default:
                return source;
        }
    };

    /**
     * 获取主机上所有独立显卡信息
     * @returns {Promise} AppoxGraphicCardInfo数组: 独立显卡信息列表
     */
    this.getGraphicCardInfos = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGraphicCardInfos", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetGraphicCardInfosOutput.decode(outputBytes);
        let result = [];
        let gcv = appoxEnums.GraphicCardVendor;
        output.infos.forEach((info) => {
            let dstVendor = appoxi7.stringToEnum(gcv, info.vendor, null);
            if (dstVendor != null) {
                result.push(new AppoxGraphicCardInfo(info.vendor, info.cardIndex, appoxi7.longToBigInt(info.memoryCapacity, 0), appoxi7.longToBigInt(info.memoryFree, 0)));
            }
        });
        return result;
    };

    /**
     * 获取图表报告ID列表
     * @returns {Promise} number数组: 图表报告ID列表
     */
    this.getGraphIDList = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGraphIDList", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.IntArray.decode(outputBytes);
        return output.intArray;
    };

    /**
     * 获取指定图表报告标题的报告ID
     * @param {string} title 图表报告标题
     * @returns {Promise} number: 图表报告ID
     */
    this.getGraphIDWithTitle = async (title) => {
        let inputBytes = appoxi7.stringToBytes(title);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGraphIDWithTitle", title, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.OptionalInt.decode(outputBytes);
        return output.value;
    };

    /**
     * 获取指定ID图表的标题
     * @param {number} graphID 图表报告ID
     * @returns {Promise} string: 图表报告标题，若不存在则返回null
     */
    this.getGraphTitle = async (graphID) => {
        let inputBytes = appoxi7.intToBytes(graphID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGraphTitle", graphID, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取指定ID图表的类别
     * @param {number} graphID 图表报告ID
     * @returns {Promise} GraphType: 图表报告类别，若不存在则返回GraphType.Invalid
     */
    this.getGraphType = async (graphID) => {
        let inputBytes = appoxi7.intToBytes(graphID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetGraphType", graphID, () => inputBytes);
        let gt = appoxEnums.GraphType;
        return appoxi7.bytesToEnum(gt, outputBytes, gt.Invalid);
    }

    /**
     * 获取session的主机Posix时间模型
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} AppoxPosixTimeModel: 主机Posix时间模型
     */
    this.getHostPosixTimeModel = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetHostPosixTimeModel", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.PosixTimeModel.decode(outputBytes);
        return new AppoxPosixTimeModel(appoxi7.longToBigInt(output.startPosix, 0), output.timeRatio);
    };

    /**
     * 获取兴趣时间点目标
     * @returns {Promise} number: 兴趣时间点目标(秒单位)，可能超出数据缓存范围
     */
    this.getInterestTarget = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetInterestTarget", null, null);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取应用程序当前兴趣点在时间线上的位置
     * @returns {Promise} number: 在时间线上的兴趣点，单位秒
     */
    this.getInterestTime = async () => {
        let outputBytes = await appoxi7.counterRunCallAgency("GetInterestTime", null, false, true);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取应用程序当前兴趣点对应的本地时间
     * @returns {Promise} Date: 兴趣点的本地时间，若无数据则返回null
     */
    this.getInterestTimestamp = async () => {
        let outputBytes = await appoxi7.counterRunCallAgency("GetInterestTimestamp", null, false, true);
        return appoxi7.stringToDate(appoxi7.bytesToString(outputBytes, null));
    };

    /**
     * 获取从互联网获取的当前时间
     * @returns {Promise} Date: 从互联网获取的当前时间(UTC时间)，若未联网或获取失败则返回null
     */
    this.getInternetNTPTime = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetInternetNTPTime", null, null);
        return appoxi7.stringToDate(appoxi7.bytesToString(outputBytes, null));
    };

    /**
     * 获取被许可的功能序号列表
     * @returns {Promise} number数组: 被许可的功能序号列表
     */
    this.getLicensedFunctionIndices = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetLicensedFunctionIndices", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.IntArray.decode(outputBytes);
        return output.intArray;
    };

    /**
     * 获取许可证的详细信息
     * @returns {Promise} string: 许可证的详细信息
     */
    this.getLicenseInfo = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetLicenseInfo", null, null);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 计算session中某个时间偏置对应的本地时间
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {number} timeOffset 时间偏置，单位秒
     * @param {boolean} useGNSS 是否使用卫星Posix时间模型计算，否则使用主机Posix时间模型
     * @returns {Promise} Date: 对应的本地时间
     */
    this.getLocalDateTime = async (session, timeOffset, useGNSS) => {
        if (session == null || !session.isValid) return null;
        let input = { session: session.toString(), timeOffset: timeOffset, useGnss: useGNSS };
        let inputBytes = appoxi7.GetDateTimeInput.encode(input).finish();
        let id = session.toString() + ":" + timeOffset + ":" + (useGNSS ? 1 : 0);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetLocalDateTime", id, () => inputBytes);
        return appoxi7.stringToDate(appoxi7.bytesToString(outputBytes, null));
    };

    /**
     * 获取所有清单信息
     * @returns {Promise} AppoxLogMessage数组: 清单信息列表
     */
    this.getLogMessages = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetLogMessages", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetLogMessagesOutput.decode(outputBytes);
        let result = [];
        let ll = appoxEnums.LogLevel;
        output.messages.forEach((msg) => {
            let dstType = appoxi7.stringToEnum(ll, msg.type, null);
            if (dstType != null) {
                result.push(new AppoxLogMessage(dstType, msg.text, appoxi7.stringToDate(msg.time), msg.repeatedCount));
            }
        });
        return result;
    };

    /**
     * 获得手动触发器所有通道的名称
     * @returns {Promise} string数组: 名称列表
     */
    this.getManualTriggerNames = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetManualTriggerNames", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获得手动触发器通道的名称
     * @param {number} index 通道序号，0~15
     * @returns {Promise} string: 手动触发器通道的名称，若序号超出范围则返回null
     */
    this.getManualTriggerName = async (index) => {
        let inputBytes = appoxi7.intToBytes(index);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetManualTriggerName", index, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取数据处理/原生/设备组件各子功能配置的状态
     * @param {string} controller 控制者名称
     * @param {string} classID 组件的类别ID
     * @returns {Promise} ConfigStatus数组: 各子功能配置的状态，若找不到类别ID对应的组件或无子功能配置则返回null
     */
    this.getModuleChildConfigStatus = async (controller, classID) => {
        let input = { item1: controller, item2: classID };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetModuleChildConfigStatus", classID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetModuleChildConfigStatusOutput.decode(outputBytes);
        let result = [];
        let cs = appoxEnums.ConfigStatus;
        output.childrenStatus.forEach((status) => {
            result.push(appoxi7.stringToEnum(cs, status, cs.Disabled));
        });
        return result;
    };

    /**
     * 获取数据处理/原生/设备组件配置的字符串描述
     * @param {string} controller 控制者名称
     * @param {string} classID 组件的类别ID
     * @returns {Promise} string: 配置的字符串描述，null表示找不到类别ID对应的组件
     */
    this.getModuleConfig = async (controller, classID) => {
        let input = { item1: controller, item2: classID };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetModuleConfig", classID, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取数据处理/原生/设备组件配置的状态
     * @param {string} controller 控制者名称
     * @param {string} classID 组件的类别ID
     * @returns {Promise} Array: [组件配置的状态(ConfigStatus), 错误提示]
     */
    this.getModuleConfigStatus = async (controller, classID) => {
        let input = { item1: controller, item2: classID };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetModuleConfigStatus", classID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetModuleConfigStatusOutput.decode(outputBytes);
        let cs = appoxEnums.ConfigStatus;
        return [ appoxi7.stringToEnum(cs, output.item1, null), output.item2 ];
    };

    /**
     * 获取数据处理/原生/设备组件的详情
     * @param {string} classID 组件的类别ID
     * @returns {Promise} AppoxModuleDetails: 组件详情，若不存在则返回null
     */
    this.getModuleDetails = async (classID) => {
        let inputBytes = appoxi7.stringToBytes(classID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetModuleDetails", classID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.ModuleDetails.decode(outputBytes);
        let outputSignals = {};
        for (let key in output.outputSignals) {
            outputSignals[key] = output.outputSignals[key].stringArray;
        }
        let requiredVideoChannels = [];
        let uvc = appoxEnums.UsingVideoChannel;
        output.requiredVideoChannels.forEach((srcChannel) => {
            let dstChannel = appoxi7.stringToEnum(uvc, srcChannel, null);
            if (dstChannel != null) requiredVideoChannels.push(dstChannel);
        });
        let requiredRecordDataTypes = [];
        let rdt = appoxEnums.RecordDataType;
        output.requiredRecordDataTypes.forEach((srcType) => {
            let dstType = appoxi7.stringToEnum(rdt, srcType, rdt.Invalid);
            if (dstType != null) requiredRecordDataTypes.push(dstType);
        });
        return new AppoxModuleDetails(output.outputSamples, output.outputScenes, outputSignals, output.outputGraphs, output.requiredSignalPackings, requiredVideoChannels, requiredRecordDataTypes);
    };

    /**
     * 获取原生组件信息表
     * @returns {Promise} Object: 原生组件信息表，键为组件ID，值为AppoxNativeClassInfo
     */
    this.getNativeClassTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetNativeClassTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetNativeClassTableOutput.decode(outputBytes);
        let result = {};
        let nlt = appoxEnums.NativeLibraryType;
        for (let key in output.infoMap) {
            let srcInfo = output.infoMap[key];
            let versions = {};
            for (let typeKey in srcInfo.libraryVersions) {
                let type = appoxi7.stringToEnum(nlt, typeKey, null);
                if (type != null) versions[type] = srcInfo.libraryVersions[typeKey];
            }
            let debugIDs = {};
            for (let typeKey in srcInfo.libraryDebugIds) {
                let type = appoxi7.stringToEnum(nlt, typeKey, null);
                if (type != null) debugIDs[type] = srcInfo.libraryDebugIds[typeKey];
            }
            result[key] = new AppoxNativeClassInfo(srcInfo.ownerPluginId, srcInfo.id, srcInfo.title, srcInfo.nativeType, versions, debugIDs);
        }
        return result;
    };

    /**
     * 获取原生插件插件版本列表
     * @param {NativeLibraryType} type 原生库类别
     * @returns {Promise} Object: 版本列表，键为原生插件的类型ID
     */
    this.getNativePluginVersions = async (type) => {
        let inputBytes = appoxi7.enumToBytes(type);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetNativePluginVersions", type, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringStringMap.decode(outputBytes);
        return output.stringMap;
    };

    /**
     * 获取所有插件的客机同步ID以及对应的标题
     * @returns {Promise} Object: 客机同步标题表，键为客机同步ID，值为对应的标题
     */
    this.getPluginGuestSyncTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetPluginGuestSyncTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringStringMap.decode(outputBytes);
        return output.stringMap;
    };

    /**
     * 获取插件包ID列表
     * @returns {Promise} string数组: 插件包ID列表
     */
    this.getPluginPackIDList = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetPluginPackIDList", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取插件包信息
     * @param {string} packID 插件包ID
     * @returns {Promise} AppoxPluginPackInfo: 插件包信息，若无对应插件包则返回null
     */
    this.getPluginPackInfo = async (packID) => {
        let inputBytes = appoxi7.stringToBytes(packID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetPluginPackInfo", packID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.PluginPackInfo.decode(outputBytes);
        let pps = appoxEnums.PluginPackStatus;
        let ppe = appoxEnums.PluginPackError;
        return new AppoxPluginPackInfo(output.id, output.name, output.version, output.brief, appoxi7.stringToEnum(pps, output.status, pps.Disabled), appoxi7.stringToEnum(ppe, output.error, ppe.LoadFailed), output.appLayerDetails, output.nativeLayerDetails);
    };

    /**
     * 获取所有插件使用的第三方软件版权声明
     * @returns {Promise} Object: 键为插件ID，值为该插件使用的第三方软件版权声明(其中键为标题，值为版权声明)
     */
    this.getPluginThirdPartyNotices = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetPluginThirdPartyNotices", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetPluginThirdPartyNoticesOutput.decode(outputBytes);
        let result = {};
        for (let key in output.notices) {
            result[key] = output.notices[key].stringMap;
        }
        return result;
    };

    /**
     * 获取距离指定时间最近的视频帧的预览JPEG图像数据
     * @param {number} channel 视频通道，0~23
     * @param {number} timeline 获取视频帧的目标时间线，单位秒
     * @param {number} maxGap 容许的最大间隔，单位秒
     * @returns {Promise} Array: [视频帧的预览JPEG数据, 图像的时间戳, 摄像头信息]，获取失败则各项为null
     */
    this.getPreviewJpeg = async (channel, timeline, maxGap) => {
        let input = { channel: channel, timeline: timeline, maxGap: maxGap };
        let inputBytes = appoxi7.GetPreviewJpegInput.encode(input).finish();
        let id = channel + ":" + timeline + ":" + maxGap;
        let outputBytes = await appoxi7.defaultGetCallAgency("GetPreviewJpeg", id, () => inputBytes);
        if (outputBytes == null) return [ null, null, null ];
        let output = appoxi7.GetPreviewJpegOutput.decode(outputBytes);
        return [ output.item1, appoxi7.convToAppoxTimestamp(output.item2), appoxi7.convToAppoxCameraInfo(output.item3) ];
    };

    /**
     * 获取数据处理组件信息表
     * @returns {Promise} Object: 数据处理组件信息表，键为组件ID
     */
    this.getProcessorClassTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetProcessorClassTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetProcessorClassTableOutput.decode(outputBytes);
        let result = {};
        for (let key in output.infoMap) {
            let srcInfo = output.infoMap[key];
            result[key] = new AppoxProcessorClassInfo(srcInfo.ownerPluginId, srcInfo.id, srcInfo.title);
        }
        return result;
    };

    /**
     * 获取原始数据通道延迟配置
     * @param {string} id 原始数据通道ID
     * @returns {Promise} number: 延迟配置，单位毫秒
     */
    this.getRawChannelDelayConfig = async (id) => {
        let inputBytes = appoxi7.stringToBytes(id);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetRawChannelDelayConfig", id, () => inputBytes);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取原始数据通道状态
     * @param {string} channelID 原始数据通道ID
     * @param {number} toleranceMillisecond 无数据的容忍时长
     * @returns {Promise} boolean: 是否有数据
     */
    this.getRawChannelStatus = async (channelID, toleranceMillisecond) => {
        let input = { item1: channelID, item2: toleranceMillisecond };
        let inputBytes = appoxi7.StringAndOptionalUInt.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetRawChannelStatus", channelID, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取样本数据通道状态
     * @param {string} channelID 样本数据通道ID
     * @param {number} toleranceMillisecond 无数据的容忍时长
     * @returns {Promise} Array: [是否有数据, 每帧数据之间的时间间隔曲线(秒), 每帧数据的延迟曲线(秒)]
     */
    this.getSampleChannelStatus = async (channelID, toleranceMillisecond) => {
        let input = { item1: channelID, item2: toleranceMillisecond };
        let inputBytes = appoxi7.StringAndOptionalUInt.encode(input).finish();
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSampleChannelStatus", channelID, () => inputBytes);
        if (outputBytes == null) return [ false, null, null ];
        let output = appoxi7.GetChannelStatusOutput.decode(outputBytes);
        return [ output.item1, output.item2, output.item3 ];
    };

    /**
     * 获取指定样本通道对应的标题
     * @param {string} channelID 样本通道ID，格式为"协议名@通道序号"
     * @returns {Promise} string数组: 样本标题，null表示通道不存在或该样本通道无标题
     */
    this.getSampleTitle = async (channelID) => {
        let inputBytes = appoxi7.stringToBytes(channelID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSampleTitle", channelID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取所有场景ID列表
     * @returns {Promise} string数组: 场景ID列表
     */
    this.getSceneIDList = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSceneIDList", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取场景标题表
     * @returns {Promise} Object: 场景标题表
     */
    this.getSceneTitleTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSceneTitleTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringStringMap.decode(outputBytes);
        return output.stringMap;
    };

    /**
     * 获取session的注释说明
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} string: Session的注释说明
     */
    this.getSessionComment = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionComment", session, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取所有session的筛选标志位
     * @returns {Promise} Object: session的筛选标志位表，键为Session字符串，值为AppoxSessionFilterFlags
     */
    this.getSessionFilterTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionFilterTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetSessionFilterTableOutput.decode(outputBytes);
        let result = {};
        for (let key in output.flagsMap) {
            let session = new AppoxSessionIdentifier(key);
            if (session.isValid) {
                let srcFlags = output.flagsMap[key];
                result[session.toString()] = new AppoxSessionFilterFlags(srcFlags.searchTrue, srcFlags.checkTrue);
            }
        }
        return result;
    };

    /**
     * 获取某个session的文件夹名
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} string: 文件夹名
     */
    this.getSessionFolderName = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionFolderName", session, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取当前层级下指定session下的所有generation ID
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} string数组: Generation ID列表
     */
    this.getSessionGenerations = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionGenerations", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取session采集时主机是否与授时服务器同步
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} boolean: 主机是否与授时服务器同步
     */
    this.getSessionHostSync = async (session) => {
        if (session == null || !session.isValid) return false;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionHostSync", session, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取某个session所属数据层级
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} string: 数据层级，其中'.'表示根路径下的session，'..'表示根路径即session
     */
    this.getSessionLayer = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionLayer", session, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取指定session的长度
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} number: Session长度，单位秒，session不存在或不属于当前层级则返回null
     */
    this.getSessionLength = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionLength", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.OptionalDouble.decode(outputBytes);
        return output.value;
    };

    /**
     * 获取当前数据层级下的所有session
     * @returns {Promise} AppoxSessionIdentifier数组: Session ID列表
     */
    this.getSessionList = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionList", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        let result = [];
        output.stringArray.forEach((str) => {
            let dstSession = new AppoxSessionIdentifier(str);
            if (dstSession.isValid) result.push(dstSession);
        });
        return result;
    };

    /**
     * 获取当前层级下所有session的总时长（未筛选）
     * @returns {Promise} number: 所有session的总时长
     */
    this.getSessionListTotalLength = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionListTotalLength", null, null);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取session的属性表
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} Object: Session的属性表
     */
    this.getSessionProperties = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionProperties", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringStringMap.decode(outputBytes);
        return output.stringMap;
    };

    /**
     * 获取当前的session搜索关键字
     * @returns {Promise} string: 当前的session搜索关键字
     */
    this.getSessionSearchKey = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionSearchKey", null, null);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取指定session在时间线上的开始时间点
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} number: 在时间线上的开始时间点，session不存在或不属于当前层级则返回null
     */
    this.getSessionTimeline = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSessionTimeline", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.OptionalDouble.decode(outputBytes);
        return output.value;
    };

    /**
     * 获取指定ID信号的名称
     * @param {string} signalID 信号ID
     * @param {boolean} fullName 是否返回完整名称
     * @returns {Promise} string: 信号名称，若无该ID信号则返回null
     */
    this.getSignalName = async (signalID, fullName) => {
        let input = { item1: signalID, item2: fullName };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        let id = signalID + ":" + (fullName ? 1 : 0);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSignalName", id, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取指定报文ID下的所有信号名称
     * @param {string} messageID 报文的全局唯一ID
     * @returns {Promise} string数组: 信号列表，若该报文不存在则返回null
     */
    this.getSignalNamesOfBusMessage = async (messageID) => {
        let inputBytes = appoxi7.stringToBytes(messageID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSignalNamesOfBusMessage", messageID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.StringArray.decode(outputBytes);
        return output.stringArray;
    };

    /**
     * 获取信号树
     * @returns {Promise} AppoxSignalTreeNode数组: 信号树根节点下的所有子节点
     */
    this.getSignalTree = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSignalTree", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetSignalTreeOutput.decode(outputBytes);
        let result = [];
        output.rootNodes.forEach((srcNode) => {
            let dstNode = appoxi7.convToAppoxSignalTreeNode(srcNode);
            if (dstNode != null) result.push(dstNode);
        });
        return result;
    };

    /**
     * 获取系统状态信息
     * @param {SystemStatus} status 系统状态类别
     * @returns {Promise} string: 系统状态信息，若无有效信息则返回null
     */
    this.getSystemStatus = async (status) => {
        let inputBytes = appoxi7.enumToBytes(status);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSystemStatus", status, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取系统状态详情
     * @param {SystemStatus} status 系统状态类别
     * @returns {Promise} string: 系统状态详情，若无有效信息则返回null
     */
    this.getSystemStatusDetails = async (status) => {
        let inputBytes = appoxi7.enumToBytes(status);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetSystemStatusDetails", status, () => inputBytes);
        return appoxi7.bytesToString(outputBytes, null);
    };

    /**
     * 获取独立任务组件信息
     * @param {string} taskClassID 独立任务组件ID
     * @returns {Promise} AppoxTaskClassInfo: 独立任务组件信息，若未找到返回null
     */
    this.getTaskClassInfo = async (taskClassID) => {
        let inputBytes = appoxi7.stringToBytes(taskClassID);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetTaskClassInfo", taskClassID, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.TaskClassInfo.decode(outputBytes);
        return new AppoxTaskClassInfo(output.ownerPluginId, output.id, output.title, output.defaultConfig);
    };

    /**
     * 获取独立任务组件信息表
     * @returns {Promise} Object: 独立任务组件信息表，键为组件ID，值为AppoxTaskClassInfo
     */
    this.getTaskClassTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetTaskClassTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetTaskClassTableOutput.decode(outputBytes);
        let result = {};
        for (let key in output.infoMap) {
            let srcInfo = output.infoMap[key];
            result[key] = new AppoxTaskClassInfo(srcInfo.ownerPluginId, srcInfo.id, srcInfo.title, srcInfo.defaultConfig);
        }
        return result;
    };

    /**
     * 计算session中某个时间偏置对应的UTC时间
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {number} timeOffset 时间偏置，单位秒
     * @param {boolean} useGNSS 是否使用卫星Posix时间模型计算，否则使用主机Posix时间模型
     * @returns {Promise} Date: 对应的UTC时间
     */
    this.getUTCDateTime = async (session, timeOffset, useGNSS) => {
        if (session == null || !session.isValid) return null;
        let input = { session: session.toString(), timeOffset: timeOffset, useGnss: useGNSS };
        let inputBytes = appoxi7.GetDateTimeInput.encode(input).finish();
        let id = session.toString() + ":" + timeOffset + ":" + (useGNSS ? 1 : 0);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetUTCDateTime", id, () => inputBytes);
        return appoxi7.stringToDate(appoxi7.bytesToString(outputBytes, null));
    };

    /**
     * 获取软件版本信息总表
     * @returns {Promise} Object: 软件版本信息总表，键为模块名，值为版本号
     */
    this.getVersionTable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVersionTable", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.StringStringMap.decode(outputBytes);
        return output.stringMap;
    };

    /**
     * 获取视频数据通道延迟配置
     * @param {number} channel 视频通道，0~23
     * @returns {Promise} number: 延迟配置，单位毫秒
     */
    this.getVideoChannelDelayConfig = async (channel) => {
        let inputBytes = appoxi7.intToBytes(channel);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVideoChannelDelayConfig", channel, () => inputBytes);
        return appoxi7.bytesToDouble(outputBytes, 0);
    };

    /**
     * 获取指定session的所有视频通道的信息
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {Promise} AppoxVideoChannelInfo数组: 该session的所有视频通道的信息，若不存在则返回null
     */
    this.getVideoChannelsInfo = async (session) => {
        if (session == null || !session.isValid) return null;
        let inputBytes = appoxi7.sessionToBytes(session);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVideoChannelsInfo", session, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.GetVideoChannelsInfoOutput.decode(outputBytes);
        let result = [];
        output.infos.forEach((srcInfo) => {
            result.push(new AppoxVideoChannelInfo(appoxi7.convToAppoxVideoInputMode(srcInfo.inputMode), srcInfo.recordFps, srcInfo.recordFpsAlign));
        });
        return result;
    };

    /**
     * 获取视频数据通道状态
     * @param {number} channel 视频通道，0~23
     * @param {number} toleranceMillisecond 无数据的容忍时长
     * @returns {Promise} Array: [是否有数据, 每帧数据之间的时间间隔曲线(秒), 每帧数据的延迟曲线(秒)]
     */
    this.getVideoChannelStatus = async (channel, toleranceMillisecond) => {
        let input = { channel: channel, toleranceMillisecond: toleranceMillisecond };
        let inputBytes = appoxi7.GetChannelStatusInput.encode(input).finish();
        let id = channel + "." + toleranceMillisecond;
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVideoChannelStatus", id, () => inputBytes);
        if (outputBytes == null) return [ false, null, null ];
        let output = appoxi7.GetChannelStatusOutput.decode(outputBytes);
        return [ output.item1, output.item2, output.item3 ];
    };

    /**
     * 获取视频设备列表
     * @returns {Promise} AppoxVideoDeviceInfo数组: 视频设备列表
     */
    this.getVideoDevices = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVideoDevices", null, null);
        if (outputBytes == null) return null;
        let output = appoxi7.GetVideoDevicesOutput.decode(outputBytes);
        let result = [];
        let vdc = appoxEnums.VideoDataCodec;
        output.infoMap.forEach((pair) => {
            let srcID = pair.key;
            let srcInfo = pair.value;
            let dstID = new AppoxVideoDeviceID(srcID.type, srcID.localId);
            let dstInputModes = [];
            srcInfo.inputModes.forEach((srcMode) => {
                let dstMode = appoxi7.convToAppoxVideoInputMode(srcMode);
                if (dstMode.inputCodec != vdc.Invalid) dstInputModes.push(dstMode);
            });
            let dstOutputModes = [];
            srcInfo.outputModes.forEach((srcMode) => {
                let dstCodec = this.stringToEnum(vdc, srcMode.inputCodec, vdc.Invalid);
                if (dstCodec != vdc.Invalid) {
                    let srcSize = srcMode.size;
                    let dstMode = new AppoxVideoOutputMode(dstCodec, new AppoxIntSize(srcSize.width, srcSize.height));
                    dstOutputModes.push(dstMode);
                }
            });
            let dstInfo = new AppoxVideoDeviceInfo(dstID, srcInfo.hardwareInfo, dstInputModes, dstOutputModes);
            result.push(dstInfo);
        });
        return result;
    };

    /**
     * 获取距离指定时间最近的视频帧数据
     * @param {number} channel 视频通道，0~23
     * @param {number} timeline 获取视频帧的目标时间线，单位秒
     * @param {number} maxGap 容许的最大间隔，单位秒
     * @param {VideoFrameGetMode} mode 获取视频帧的模式
     * @param {AppoxIntRect} clip 在输出模式基础上进一步裁剪，为原始尺寸坐标系，null表示完整输出
     * @param {boolean} withAlpha 是否输出带Alpha通道的图像
     * @returns {Promise} Array: [视频帧数据, 图像的时间戳, 摄像头信息]，获取失败则各项为null
     */
    this.getVideoFrameImage = async (channel, timeline, maxGap, mode, clip, withAlpha) => {
        let inputClip = null;
        let clipText = "";
        if (clip != null && clip instanceof AppoxIntRect) {
            inputClip = { x: clip.x, y: clip.y, width: clip.width, height: clip.height };
            clipText = clip.x + "," + clip.y + "," + clip.width + "," + clip.height;
        }
        let input = { channel: channel, timeline: timeline, maxGap: maxGap, mode: mode, clip: inputClip, withAlpha: withAlpha };
        let inputBytes = appoxi7.GetVideoFrameImageInput.encode(input).finish();
        let id = channel + ":" + timeline + ":" + maxGap + ":" + mode + ":" + clipText + ":" + (withAlpha ? 1 : 0);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVideoFrameImage", id, () => inputBytes);
        if (outputBytes == null) return [ null, null, null ];
        let output = appoxi7.GetVideoFrameImageOutput.decode(outputBytes);
        return [ appoxi7.convToAppoxCommonImage(output.item1), appoxi7.convToAppoxTimestamp(output.item2), appoxi7.convToAppoxCameraInfo(output.item3) ];
    };

    /**
     * 获取距离指定时间最近的缩略图数据
     * @param {number} channel 视频通道，0~23
     * @param {number} timeline 获取视频帧的目标时间线，单位秒
     * @param {number} maxGap 容许的最大间隔，单位秒
     * @param {boolean} withAlpha 是否输出带Alpha通道的图像
     * @returns {Promise} AppoxCommonImage: 缩略图数据，图像宽度固定为80，获取失败则返回null
     */
    this.getVideoFrameThumbnail = async (channel, timeline, maxGap, withAlpha) => {
        let input = { channel: channel, timeline: timeline, maxGap: maxGap, withAlpha: withAlpha };
        let inputBytes = appoxi7.GetVideoFrameThumbnailInput.encode(input).finish();
        let id = channel + ":" + timeline + ":" + maxGap + ":" + (withAlpha ? 1 : 0);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVideoFrameThumbnail", id, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.CommonImage.decode(outputBytes);
        return appoxi7.convToAppoxCommonImage(output);
    };

    /**
     * 获取指定通道在指定时间上的视频帧的原始尺寸
     * @param {number} channel 视频通道，0~23
     * @param {number} timeline 获取视频帧的目标时间线，单位秒
     * @returns {Promise} AppoxIntSize: 原始尺寸，若无数据则返回null
     */
    this.getVideoRawSize = async (channel, timeline) => {
        let input = { item1: channel, item2: timeline };
        let inputBytes = appoxi7.IntAndDouble.encode(input).finish();
        let id = channel + ":" + timeline;
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVideoRawSize", id, () => inputBytes);
        if (outputBytes == null) return null;
        let output = appoxi7.IntSize.decode(outputBytes);
        return new AppoxIntSize(output.width, output.height);
    };

    /**
     * 获取视频通道的特殊摄像头类型
     * @param {number} channel 视频通道，0~23
     * @returns {Promise} SpecialCameraType: 特殊摄像头类型
     */
    this.getVideoSpecialType = async (channel) => {
        let inputBytes = appoxi7.intToBytes(channel);
        let outputBytes = await appoxi7.defaultGetCallAgency("GetVideoSpecialType", channel, () => inputBytes);
        let sct = appoxEnums.SpecialCameraType;
        return appoxi7.bytesToEnum(sct, outputBytes, sct.Unknown);
    };

    /**
     * 获取总线报文是否已绑定发送
     * @param {string} busMessageID 总线报文ID
     * @returns {Promise} boolean: 是否已绑定
     */
    this.isBusMessageBound = async (busMessageID) => {
        let inputBytes = appoxi7.stringToBytes(busMessageID);
        let outputBytes = await appoxi7.defaultGetCallAgency("IsBusMessageBound", busMessageID, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取组件是否支持禁用
     * @param {string} classID 组件的类别ID
     * @returns {Promise} boolean: 是否支持禁用
     */
    this.isDisableModuleSupported = async (classID) => {
        let inputBytes = appoxi7.stringToBytes(classID);
        let outputBytes = await appoxi7.defaultGetCallAgency("IsDisableModuleSupported", classID, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取组件是否支持启用
     * @param {string} classID 组件的类别ID
     * @returns {Promise} boolean: 是否支持启用
     */
    this.isEnableModuleSupported = async (classID) => {
        let inputBytes = appoxi7.stringToBytes(classID);
        let outputBytes = await appoxi7.defaultGetCallAgency("IsEnableModuleSupported", classID, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 检查是否为在线采集或离线处理生成模式
     * @returns {Promise} boolean: 是否为在线采集或离线处理生成模式
     */
    this.isFileOutputEnabled = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("IsFileOutputEnabled", null, null);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 检查指定的输入样本通道是否可用
     * @param {string} channelID 样本通道ID，格式为"协议名@通道序号"
     * @returns {Promise} boolean: 该通道是否可用
     */
    this.isInputChannelAvailable = async (channelID) => {
        let inputBytes = appoxi7.stringToBytes(channelID);
        let outputBytes = await appoxi7.defaultGetCallAgency("IsInputChannelAvailable", channelID, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 当前互联网是否已连接
     * @returns {Promise} boolean: 是否已连接互联网
     */
    this.isInternetConnected = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("IsInternetConnected", null, null);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 查看报文配置是否有效
     * @param {string} messageID 报文的全局唯一ID
     * @param {boolean} optional 是否为可选配置
     * @returns {Promise} boolean: 是否有效
     */
    this.isMessageValid = async (messageID, optional) => {
        let input = { item1: messageID, item2: optional };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        let id = messageID + ":" + (optional ? 1 : 0);
        let outputBytes = await appoxi7.defaultGetCallAgency("IsMessageValid", id, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取是否使用境内网络服务
     * @returns {Promise} boolean: 是否使用境内网络服务
     */
    this.isPRCWebPreferred = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("IsPRCWebPreferred", null, null);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 返回是否允许进行保存工程项目和开始session等操作，若不允许则输出繁忙原因
     * @returns {Promise} Array: [是否允许进行保存工程项目和开始session等操作, 系统繁忙原因]
     */
    this.isReady = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("IsReady", null, null);
        if (outputBytes == null) return [ false, null ];
        let output = appoxi7.StringAndBool.decode(outputBytes);
        return [ output.item2, output.item1 ];
    };

    /**
     * 检查样本数据通道是否冲突
     * @param {string} channelID 样本通道协议，格式为"协议名@通道序号"
     * @returns {Promise} boolean: 是否冲突
     */
    this.isSampleChannelConflict = async (channelID) => {
        let inputBytes = appoxi7.stringToBytes(channelID);
        let outputBytes = await appoxi7.defaultGetCallAgency("IsSampleChannelConflict", channelID, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 查看信号配置是否有效
     * @param {string} signalID 信号的全局唯一ID
     * @param {boolean} optional 是否为可选配置
     * @returns {Promise} boolean: 是否有效
     */
    this.isSignalValid = async (signalID, optional) => {
        let input = { item1: signalID, item2: optional };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        let id = signalID + ":" + (optional ? 1 : 0);
        let outputBytes = await appoxi7.defaultGetCallAgency("IsSignalValid", id, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 获取是否可以无缝切换下一段session预览或采集（仅限在线模式）
     * @returns {Promise} boolean: 是否可以无缝切换下一段session
     */
    this.isSwitchSessionAvailable = async () => {
        let outputBytes = await appoxi7.defaultGetCallAgency("IsSwitchSessionAvailable", null, null);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 查看指定视频通道是否有数据可供显示
     * @param {number} channel 视频通道，0~23
     * @param {number} tolerance 可容忍最近多少毫秒（现实时间）无数据
     * @returns {Promise} boolean: 是否有数据可供显示
     */
    this.isVideoDataAvailable = async (channel, tolerance) => {
        let input = { item1: channel, item2: tolerance };
        let inputBytes = appoxi7.IntAndUInt.encode(input).finish();
        let id = channel + ":" + (tolerance == null ? "" : tolerance);
        let outputBytes = await appoxi7.defaultGetCallAgency("IsVideoDataAvailable", id, () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 解析总线报文，获取信号值等信息
     * @param {AppoxBusMessageSample} busMessage 总线报文
     * @returns {Promise} AppoxBusSignalValue数组: 所有信号值及相关信息
     */
    this.parseBusMessage = async (busMessage) => {
        if (busMessage == null || !(busMessage instanceof AppoxBusMessageSample)) return null;
        let input = { channel: busMessage.channel, length: busMessage.length, id: busMessage.id, data: busMessage.data, type: busMessage.type, state: busMessage.state, schedulePosixTime: appoxi7.bigIntToLong(busMessage.schedulePosixTime, false) };
        let inputBytes = appoxi7.BusMessageSample.encode(input).finish();
        let outputBytes = await appoxi7.counterRunCallAgency("ParseBusMessage", () => inputBytes, true, false);
        if (outputBytes == null) return null;
        let output = appoxi7.ParseBusMessageOutput.decode(outputBytes);
        let result = [];
        output.values.forEach((val) => {
            result.push(new AppoxBusSignalValue(val.signalName, val.value, val.unit, val.enumValue));
        });
        return result;
    };

    /**
     * 发布数据
     * @param {string} dataID 数据ID，不可为null或空字符串
     * @param {Uint8Array} data 数据，不可为null
     * @returns {Promise} 无参数
     */
    this.publishData = async (dataID, data) => {
        let input = { dataId: dataID, data: data };
        let inputBytes = appoxi7.PublishDataInput.encode(input).finish();
        await appoxi7.counterRunCallAgency("PublishData", () => inputBytes, true, false);
    };

    /**
     * 刷新当前层级下所有session的generation
     * @returns {Promise} 无参数
     */
    this.refreshGenerations = async () => {
        await appoxi7.defaultSetCallAgency("RefreshGenerations", null, null, false, false);
    };

    /**
     * 刷新当前层级下所有session
     * @returns {Promise} 无参数
     */
    this.refreshSessions = async () => {
        await appoxi7.defaultSetCallAgency("RefreshSessions", null, null, false, false);
    };

    /**
     * 移除总线通道引用
     * @param {number} busChannel 总线通道，1~48
     * @returns {Promise} 无参数
     */
    this.removeBusMessageReference = async (busChannel) => {
        let inputBytes = appoxi7.intToBytes(busChannel);
        await appoxi7.counterRunCallAgency("RemoveBusMessageReference", () => inputBytes, false, false);
    };

    /**
     * 移除指定事件对象
     * @param {string} eventHandle 事件对象
     * @returns {Promise} 无参数
     */
    this.removeEvent = async (eventHandle) => {
        let inputBytes = appoxi7.stringToBytes(eventHandle);
        await appoxi7.defaultSetCallAgency("RemoveEvent", eventHandle, () => inputBytes, false, false);
    };

    /**
     * 移除generation及相关文件至回收站
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {string} genID Generation ID
     * @returns {Promise} 无参数
     */
    this.removeGeneration = async (session, genID) => {
        if (session == null || !session.isValid) return;
        let input = { item1: session.toString(), item2: genID };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let id = session.toString() + "." + (genID == null ? "" : genID);
        await appoxi7.defaultSetCallAgency("RemoveGeneration", id, () => inputBytes, false, false);
    };

    /**
     * 移除点云通道引用
     * @param {number} channel 点云数据通道。通道序号从0开始
     * @returns {Promise} 无参数
     */
    this.removePointCloudReference = async (channel) => {
        if (channel >= 0 && channel < appoxi7.pointCloudRefs.length) appoxi7.pointCloudRefs[channel] = Math.max(0, appoxi7.pointCloudRefs[channel] - 1);
        let inputBytes = appoxi7.intToBytes(channel);
        await appoxi7.counterRunCallAgency("RemovePointCloudReference", () => inputBytes, false, false);
    };

    /**
     * 移除session及相关文件至回收站
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {boolean} force 是否强制移除
     * @returns {Promise} boolean: 是否成功移除
     */
    this.removeSession = async (session, force) => {
        if (session == null || !session.isValid) return;
        let input = { item1: session.toString(), item2: force };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        let id = session.toString() + ":" + (force ? 1 : 0);
        let outputBytes = await appoxi7.defaultSetCallAgency("RemoveSession", id, () => inputBytes, false, true);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 移除信号引用
     * @param {string} signalID 信号ID
     * @returns {Promise} 无参数
     */
    this.removeSignalReference = async (signalID) => {
        let inputBytes = appoxi7.stringToBytes(signalID);
        await appoxi7.counterRunCallAgency("RemoveSignalReference", () => inputBytes, false, false);
    };

    /**
     * 重置GPU解码测试结果
     * @returns {Promise} 无参数
     */
    this.resetGPUDecoderTestResults = async () => {
        await appoxi7.defaultSetCallAgency("ResetGPUDecoderTestResults", null, null, false, false);
    };

    /**
     * 运行控制台过程
     * @param {string} consoleClassID 控制台组件ID
     * @returns {Promise} 无参数
     */
    this.runConsole = async (consoleClassID) => {
        let inputBytes = appoxi7.stringToBytes(consoleClassID);
        await appoxi7.exclusiveRunCallAgency("RunConsole", () => inputBytes);
    };

    /**
     * 运行一个独立处理任务，仅限空闲时运行
     * @param {string} taskClassID 任务组件的类别ID
     * @param {string} config 配置的字符串描述
     * @returns {Promise} Array: [任务运行结果(TaskResult), 任务的返回值信息]
     */
    this.runStandaloneTask = async (taskClassID, config) => {
        let input = { item1: taskClassID, item2: config };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("RunStandaloneTask", () => inputBytes);
        let tr = appoxEnums.TaskResult;
        if (outputBytes == null) return [ tr.Unknown, null ];
        let output = appoxi7.TwoStrings.decode(outputBytes);
        return [ appoxi7.stringToEnum(tr, output.item1, tr.Unknown), output.item2 ];
    };

    /**
     * 发送总线报文，可周期性发送，也可单次发送（仅在线模式可用）
     * @param {AppoxBusMessage} message 想要发送的报文信息
     * @returns {Promise} 无参数
     */
    this.sendBusMessage = async (message) => {
        if (message == null || !(message instanceof AppoxBusMessage)) return;
        let input = { channel: message.channel, id: message.id, data: message.data, millisecondInterval: message.millisecondInterval };
        let inputBytes = appoxi7.BusMessage.encode(input).finish();
        await appoxi7.counterRunCallAgency("SendBusMessage", () => inputBytes, true, false);
    };

    /**
     * 发送总线报文（该报文需设置绑定），可周期性发送，也可单次发送（仅在线模式可用）
     * @param {string} messageID 绑定的报文ID
     * @param {number} interval 报文发送周期，单位毫秒，若设为null则只发送一次
     * @returns {Promise} Uint8Array: 输出生成的报文数据，若未绑定则输出null
     */
    this.sendBusMessageBound = async (messageID, interval) => {
        let input = { item1: messageID, item2: interval };
        let inputBytes = appoxi7.StringAndOptionalUInt.encode(input).finish();
        let outputBytes = await appoxi7.counterRunCallAgency("SendBusMessageBound", () => inputBytes, false, false);
        return outputBytes;
    };

    /**
     * 发送手动触发器信号
     * @param {number} channel 手动触发器通道，0~15
     * @returns {Promise} 无参数
     */
    this.sendManualTrigger = async (channel) => {
        let inputBytes = appoxi7.intToBytes(channel);
        await appoxi7.counterRunCallAgency("SendManualTrigger", () => inputBytes, false, false);
    };

    /**
     * 发送已获取的原始数据信息（仅在线模式可用）
     * @param {string} channelID 原始数据协议名称
     * @param {number[]} values 数值数据
     * @param {Uint8Array} binary 二进制数据
     * @returns {Promise} 无参数
     */
    this.sendRawData = async (channelID, values, binary) => {
        let input = { channelId: channelID, values: values, binary: binary };
        let inputBytes = appoxi7.SendRawDataInput.encode(input).finish();
        await appoxi7.counterRunCallAgency("SendRawData", () => inputBytes, true, false);
    };

    /**
     * 设置音频数据通道延迟配置
     * @param {number} delay 延迟配置，单位毫秒
     * @returns {Promise} 无参数
     */
    this.setAudioChannelDelayConfig = async (delay) => {
        let inputBytes = appoxi7.doubleToBytes(delay);
        await appoxi7.defaultSetCallAgency("SetAudioChannelDelayConfig", null, () => inputBytes, false, false);
    };

    /**
     * 设置总线数据通道延迟配置
     * @param {number} busChannel 总线通道，1~48
     * @param {number} delay 延迟配置，单位毫秒
     * @returns {Promise} 无参数
     */
    this.setBusChannelDelayConfig = async (busChannel, delay) => {
        let input = { item1: busChannel, item2: delay };
        let inputBytes = appoxi7.IntAndDouble.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetBusChannelDelayConfig", busChannel, () => inputBytes, false, false);
    };

    /**
     * 设置指定通道是否配置为客机同步
     * @param {string} id 客机同步ID，如bus.1, video.0, xxx.yyy等
     * @param {boolean} guestSync 是否配置为客机同步
     * @returns {Promise} 无参数
     */
    this.setChannelGuestSyncFlag = async (id, guestSync) => {
        let input = { item1: id, item2: guestSync };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetChannelGuestSyncFlag", id, () => inputBytes, false, false);
    };

    /**
     * 设置是否监控指定通道有无数据
     * @param {string} id 监控ID，如：bus@1, video@0, audio等
     * @param {boolean} monitoring 是否监控有无数据
     * @returns {Promise} 无参数
     */
    this.setChannelMonitoringFlag = async (id, monitoring) => {
        let input = { item1: id, item2: monitoring };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetChannelMonitoringFlag", id, () => inputBytes, false, false);
    };

    /**
     * 设置是否监控指定通道数据与授时服务器同步
     * @param {string} id 监控ID，如bus@1, video@0, sample@xxx-v1@0等
     * @param {boolean} monitoring 是否监控数据与授时服务器同步
     * @returns {Promise} 无参数
     */
    this.setChannelServerSyncMonitoringFlag = async (id, monitoring) => {
        let input = { item1: id, item2: monitoring };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetChannelServerSyncMonitoringFlag", id, () => inputBytes, false, false);
    };

    /**
     * 设置独占控制模式
     * @param {string} controllerName 控制者名称
     * @param {boolean} enabled 是否开启独占控制模式
     * @returns {Promise} boolean: 开启或关闭是否成功
     */
    this.setControlFlag = async (controllerName, enabled) => {
        let input = { item1: controllerName, item2: enabled };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        let outputBytes = await appoxi7.counterRunCallAgency("SetControlFlag", () => inputBytes, false, false);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 设置当前的数据层级
     * @param {string} layer 数据层级，其中null表示所有层级，'.'表示根路径下的session，'..'表示根路径即session
     * @returns {Promise} 无参数
     */
    this.setCurrentDataLayer = async (layer) => {
        let inputBytes = appoxi7.stringToBytes(layer == null ? "" : layer);
        await appoxi7.defaultSetCallAgency("SetCurrentDataLayer", null, () => inputBytes, false, false);
    };

    /**
     * 设置指定事件的注释
     * @param {string} eventHandle 事件对象
     * @param {string} comment 事件注释
     * @returns {Promise} 无参数
     */
    this.setEventComment = async (eventHandle, comment) => {
        let input = { item1: eventHandle, item2: comment };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetEventComment", eventHandle, () => inputBytes, true, false);
    };

    /**
     * 设置全局参数
     * @param {string} key 全局参数key，若为null或""则忽略
     * @param {string} value 全局参数value，若为null则忽略
     * @returns {Promise} 无参数
     */
    this.setGlobalParameter = async (key, value) => {
        let input = { item1: key, item2: value };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetGlobalParameter", key, () => inputBytes, true, false);
    };

    /**
     * 设置全局变量
     * @param {string} key 全局变量key，若为null或""则忽略
     * @param {string} value 全局变量value，若为null则忽略
     * @returns {Promise} 无参数
     */
    this.setGlobalVariable = async (key, value) => {
        let input = { item1: key, item2: value };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetGlobalVariable", key, () => inputBytes, true, false);
    };

    /**
     * 按时间线设置兴趣点
     * @param {number} targetTimeline 时间线，单位秒
     * @returns {Promise} 无参数
     */
    this.setInterestTime = async (targetTimeline) => {
        let inputBytes = appoxi7.doubleToBytes(targetTimeline);
        await appoxi7.defaultSetCallAgency("SetInterestTime", null, () => inputBytes, false, false);
    };

    /**
     * 按本地日期时间设置兴趣点
     * @param {Date} targetTimestamp 本地日期时间
     * @returns {Promise} 无参数
     */
    this.setInterestTimestamp = async (targetTimestamp) => {
        if (targetTimestamp == null || !(targetTimestamp instanceof Date)) return;
        let inputBytes = appoxi7.stringToBytes(appoxi7.dateToString(targetTimestamp));
        await appoxi7.defaultSetCallAgency("SetInterestTimestamp", null, () => inputBytes, false, false);
    };

    /**
     * 设定手动触发器通道的名称
     * @param {number} index 通道序号，0~15
     * @param {string} name 手动触发器通道的名称，若值为空则忽略
     * @returns {Promise} 无参数
     */
    this.setManualTriggerName = async (index, name) => {
        let input = { item1: index, item2: name};
        let inputBytes = appoxi7.IntAndString.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetManualTriggerName", index, () => inputBytes, false, false);
    };

    /**
     * 设置数据处理/原生/设备组件配置的字符串描述
     * @param {string} controller 控制者名称
     * @param {string} classID 组件的类别ID
     * @param {string} config 配置的字符串描述
     * @returns {Promise} 无参数
     */
    this.setModuleConfig = async (controller, classID, config) => {
        let input = { item1: controller, item2: classID, item3: config };
        let inputBytes = appoxi7.ThreeStrings.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetModuleConfig", classID, () => inputBytes, true, false);
    };

    /**
     * 设置原始数据通道延迟配置
     * @param {string} id 原始数据通道ID
     * @param {number} delay 延迟配置，单位毫秒
     * @returns {Promise} 无参数
     */
    this.setRawChannelDelayConfig = async (id, delay) => {
        let input = { item1: id, item2: delay };
        let inputBytes = appoxi7.StringAndDouble.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetRawChannelDelayConfig", id, () => inputBytes, false, false);
    };

    /**
     * 设置session是否框选
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {boolean} check 是否框选
     * @returns {Promise} 无参数
     */
    this.setSessionChecker = async (session, check) => {
        if (session == null || !session.isValid) return;
        let input = { item1: session.toString(), item2: check };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetSessionChecker", session, () => inputBytes, false, false);
    };

    /**
     * 设置session的注释说明
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {string} comment Session的注释说明
     * @returns {Promise} 无参数
     */
    this.setSessionComment = async (session, comment) => {
        if (session == null || !session.isValid) return;
        let input = { item1: session.toString(), item2: comment };
        let inputBytes = appoxi7.TwoStrings.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetSessionComment", session, () => inputBytes, true, false);
    };

    /**
     * 设置session采集时主机是否与授时服务器同步
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {boolean} hostSync 主机是否与授时服务器同步
     * @returns {Promise} 无参数
     */
    this.setSessionHostSync = async (session, hostSync) => {
        if (session == null || !session.isValid) return;
        let input = { item1: session.toString(), item2: hostSync };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetSessionHostSync", session, () => inputBytes, false, false);
    };

    /**
     * 设置session的属性表
     * @param {AppoxSessionIdentifier} session Session ID
     * @param {Object} properties Session的属性表
     * @returns {Promise} 无参数
     */
    this.setSessionProperties = async (session, properties) => {
        if (session == null || !session.isValid) return;
        let input = { session: session.toString(), properties: properties };
        let inputBytes = appoxi7.SetSessionPropertiesInput.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetSessionProperties", session, () => inputBytes, true, false);
    };

    /**
     * 设置session搜索关键字
     * @param {string} keyword session搜索关键字
     * @returns {Promise} 无参数
     */
    this.setSessionSearchKeyword = async (keyword) => {
        let inputBytes = appoxi7.stringToBytes(keyword);
        await appoxi7.defaultSetCallAgency("SetSessionSearchKeyword", null, () => inputBytes, false, false);
    };

    /**
     * 设置目标回放速度，用于回放或离线处理
     * @param {number} speed 目标回放速度
     * @returns {Promise} 无参数
     */
    this.setTargetReplaySpeed = async (speed) => {
        let inputBytes = appoxi7.doubleToBytes(speed);
        await appoxi7.defaultSetCallAgency("SetTargetReplaySpeed", null, () => inputBytes, false, false);
    };

    /**
     * 设置视频数据通道延迟配置
     * @param {number} channel 视频通道，0~23
     * @param {number} delay 延迟配置，单位毫秒
     * @returns {Promise} 无参数
     */
    this.setVideoChannelDelayConfig = async (channel, delay) => {
        let input = { item1: channel, item2: delay };
        let inputBytes = appoxi7.IntAndDouble.encode(input).finish();
        await appoxi7.defaultSetCallAgency("SetVideoChannelDelayConfig", channel, () => inputBytes, false, false);
    };

    /**
     * 切换至离线模式并开始预览或后处理
     * @param {boolean} force 是否强制开始
     * @param {boolean} previewOnly 是否为预览
     * @param {string} genDirName 后处理输出时，写入generation数据的文件夹名，可为null
     * @returns {Promise} boolean: 是否成功
     */
    this.startOffline = async (force, previewOnly, genDirName) => {
        let input = { force: force, previewOnly: previewOnly, dirName: genDirName };
        let inputBytes = appoxi7.StartOfflineOnlineInput.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("StartOffline", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 切换至在线模式并开始预览或采集（带控制者名称）
     * @param {string} controllerName 控制者名称
     * @param {boolean} previewOnly 是否为预览
     * @returns {Promise} boolean: 是否成功
     */
    this.startOnlineWithController = async (controllerName, previewOnly) => {
        let input = { item1: controllerName, item2: previewOnly };
        let inputBytes = appoxi7.StringAndBool.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("StartOnlineWithController", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 切换至在线模式并开始预览或采集
     * @param {boolean} force 是否强制开始
     * @param {boolean} previewOnly 是否为预览
     * @param {string} sessionDirName 采集时，写入session数据的文件夹名，可为null
     * @returns {Promise} boolean: 是否成功
     */
    this.startOnline = async (force, previewOnly, sessionDirName) => {
        let input = { force: force, previewOnly: previewOnly, dirName: sessionDirName };
        let inputBytes = appoxi7.StartOfflineOnlineInput.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("StartOnline", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 切换至远程模式并开始预览或采集
     * @param {boolean} force 是否强制开始
     * @param {boolean} previewOnly 是否为预览
     * @param {string} sessionDirName 采集时，写入session数据的文件夹名，可为null
     * @param {bigint} startPosixTime 远程主机的开始时间，单位毫秒
     * @returns {Promise} boolean: 是否成功
     */
    this.startRemote = async (force, previewOnly, sessionDirName, startPosixTime) => {
        let input = { force: force, previewOnly: previewOnly, sessionDirName: sessionDirName, startPosixTime: appoxi7.bigIntToLong(startPosixTime) };
        let inputBytes = appoxi7.StartRemoteInput.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("StartRemote", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 切换至远程模式并开始预览或采集（带控制者名称）
     * @param {string} controllerName 控制者名称
     * @param {boolean} previewOnly 是否为预览
     * @param {bigint} startPosixTime 远程主机的开始时间，单位毫秒
     * @returns {Promise} boolean: 是否成功
     */
    this.startRemoteWithController = async (controllerName, previewOnly, startPosixTime) => {
        let input = { controllerName: controllerName, previewOnly: previewOnly, startPosixTime: appoxi7.bigIntToLong(startPosixTime) };
        let inputBytes = appoxi7.StartRemoteWithControllerInput.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("StartRemoteWithController", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 切换至回放模式并开始回放
     * @param {boolean} force 是否强制开始
     * @param {number} startTimeline 时间线上的回放开始时间，单位秒
     * @param {number} interestTarget 时间线上的目标兴趣点，单位秒（空表示不设置兴趣点）
     * @returns {Promise} boolean: 是否成功
     */
    this.startReplay = async (force, startTimeline, interestTarget) => {
        let input = { force: force, startTimeline: startTimeline, interestTarget: interestTarget };
        let inputBytes = appoxi7.StartReplayInput.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("StartReplay", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 停止Session（带控制者名称）
     * @param {string} controllerName 控制者名称
     * @returns {Promise} boolean: 是否成功
     */
    this.stopRunningWithController = async (controllerName) => {
        let inputBytes = appoxi7.stringToBytes(controllerName);
        let outputBytes = await appoxi7.exclusiveRunCallAgency("StopRunningWithController", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 停止Session
     * @param {boolean} force 是否强制结束
     * @param {boolean} editRecordedSession 成功停止后是否立即编辑session信息
     * @returns {Promise} boolean: 是否成功
     */
    this.stopRunning = async (force, editRecordedSession) => {
        let input = { item1: force, item2: editRecordedSession };
        let inputBytes = appoxi7.TwoBools.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("StopRunning", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };

    /**
     * 订阅来自应用层其他组件的数据
     * @param {string} dataID 数据ID，不可为null或空字符串
     * @param {number} bufferLength 缓存长度，范围在1~1000
     * @param {number} timeout 超过该时间不取出缓存数据则自动关闭订阅，单位秒，范围在10～600
     * @returns {Promise} AppoxDataSubscriber: 数据订阅对象，若初始化失败则返回null
     */
    this.subscribeData = async (dataID, bufferLength, timeout) => {
        let input = { dataId: dataID, bufferLength: bufferLength, timeout: timeout };
        let inputBytes = appoxi7.SubscribeDataInput.encode(input).finish();
        let outputBytes = await appoxi7.counterRunCallAgency("SubscribeData", () => inputBytes, false, false);
        let subscriberGUID = appoxi7.bytesToString(outputBytes, null);
        if (subscriberGUID == null) return null;
        let subscriber = new AppoxDataSubscriber(subscriberGUID);
        appoxi7.dataSubscribers[subscriberGUID] = subscriber;
        return subscriber;
    };

    /**
     * 切换应用程序当前的运行模式
     * @param {string} controllerName 控制者名称
     * @param {number} mode 目标运行模式
     * @param {number} waitSecond 超时，大于0有效，单位秒
     * @returns {Promise} boolean: 是否成功切换
     */
    this.switchAppMode = async (controllerName, mode, waitSecond) => {
        let input = { controllerName: controllerName, mode: mode, waitSecond: waitSecond };
        let inputBytes = appoxi7.SwitchAppModeInput.encode(input).finish();
        let outputBytes = await appoxi7.exclusiveRunCallAgency("SwitchAppMode", () => inputBytes);
        return appoxi7.bytesToBool(outputBytes);
    };
}
export { agencyAsync };

/**
 * 回调接口集
 */
var appoxCallback = new AppoxCallback();
function AppoxCallback() {

    /**
     * 弹出警告或提示对话框
     * @param {string} message 消息内容
     * @param {string} title 对话框标题
     */
    this.onAlert = (message, title) => {
    };

    /**
     * 弹出确认对话框，返回用户选择结果
     * @param {string} message 消息内容
     * @param {string} title 对话框标题
     * @returns {Promise} boolean: 用户是否确认
     */
    this.onConfirm = (message, title) => {
        return new Promise((resolve) => {
            resolve(true);
        });
    };

    /**
     * 弹出确认对话框，返回用户选择结果
     * @param {string} message 消息内容
     * @param {string} title 对话框标题
     * @param {boolean} defaultResult 默认结果
     * @returns {Promise} boolean: 用户是否确认
     */
    this.onConfirmWithDefault = (message, title, defaultResult) => {
        return new Promise((resolve) => {
            resolve(defaultResult);
        });
    };

    /**
     * 记录日志信息
     * @param {string} message 日志内容
     * @param {LogLevel} level 日志级别
     */
    this.onLog = (message, level) => {
    };

    /**
     * 输出调试信息
     * @param {string} message 调试信息
     * @param {string} source 来源
     */
    this.onDebugMessage = (message, source) => {
    };

    /**
     * 获取应用程序显示语言
     * @returns {Language} 语言枚举值
     */
    this.onGetAppLanguage = () => {
        return appoxEnums.Language.Invalid;
    };

    /**
     * 选择总线协议文件时被调用
     * @param {Array} selected AppoxBusProtocolFileID数组，已选中的协议文件
     * @returns {Promise} AppoxBusProtocolFileID数组: 返回选择结果
     */
    this.onSelectBusProtocolFiles = (selected) => {
        return new Promise((resolve) => {
            resolve(null);
        });
    };

    /**
     * 选择总线报文时被调用
     * @param {string} originID 原始报文ID
     * @returns {Promise} string: 返回选择的报文ID
     */
    this.onSelectBusMessage = (originID) => {
        return new Promise((resolve) => {
            resolve(originID);
        });
    };

    /**
     * 选择多个总线报文时被调用
     * @param {Array} selected 已选中的报文ID
     * @param {number} availableCount 可用报文数量
     * @returns {Promise} string数组: 返回选择的报文ID数组
     */
    this.onSelectBusMessages = (selected, availableCount) => {
        return new Promise((resolve) => {
            resolve([]);
        });
    };

    /**
     * 选择信号时被调用
     * @param {string} originValueID 原始主信号的全局唯一ID
     * @param {boolean} withSignBit 是否带符号位信号
     * @param {string} originSignBitID 原始符号位信号的全局唯一ID
     * @param {boolean} withScale 是否带乘数
     * @param {string} originScale 原始信号值的乘数
     * @param {string} unit 单位
     * @returns {Promise} Array: [主信号ID, 符号位信号ID, 乘数]
     */
    this.onSelectSignal = (originValueID, withSignBit, originSignBitID, withScale, originScale, unit) => {
        return new Promise((resolve) => {
            resolve([originValueID, originSignBitID, originScale]);
        });
    };

    /**
     * 选择多个信号时被调用
     * @param {Array} selected 已选中的信号ID
     * @param {number} availableCount 可用信号数量
     * @returns {Promise} string数组: 返回选择的信号ID数组
     */
    this.onSelectSignals = (selected, availableCount) => {
        return new Promise((resolve) => {
            resolve([]);
        });
    };

    /**
     * 运行独立任务时被调用
     * @param {string} title 任务标题
     * @param {string} taskClassID 任务类ID
     * @param {AppoxRunTaskCallback} callback 回调对象
     * @returns {Promise} 无参数
     */
    this.onRunStandaloneTask = (title, taskClassID, callback) => {
        return new Promise((resolve) => {
            resolve();
        });
    };

    /**
     * 重置数据时被调用
     */
    this.onResetData = () => {
    };

    /**
     * 输入数据时被调用
     * @param {object} data 输入的数据
     */
    this.onInputData = (data) => {
    };

    /**
     * 启动Session时被调用
     */
    this.onStartSession = () => {
    };

    /**
     * 停止Session时被调用
     */
    this.onStopSession = () => {
    };

    /**
     * 编辑已录制的Session
     * @param {AppoxSessionIdentifier} recordSessionID Session标识符
     */
    this.onEditRecordedSession = (recordSessionID) => {
    };

    /**
     * 检查是否优先使用境内Web服务时被调用
     * @returns {boolean} 是否优先使用境内Web服务
     */
    this.onCheckPreferPRCWeb = () => {
        return true;
    };

    /**
     * 连接状态变化时被调用
     * @param {boolean} connected 是否已连接
     */
    this.onConnectionChanged = (connected) => {
    };

    /**
     * 得到新的数据传输统计信息时被调用
     * @param {object} stat 统计信息表，键为TransferStreamType，值为AppoxTransferStatistics
     */
    this.onTransferStatistics = (stat) => {
    };

    /**
     * 请求输入数字
     * @param {string} message 提示信息
     * @param {number} defaultValue 默认值
     * @returns {Promise} number: 用户输入的数字
     */
    this.onRequestInputNumber = (message, defaultValue) => {
        return new Promise((resolve) => {
            resolve(defaultValue);
        });
    };

    /**
     * 请求输入字符串
     * @param {string} message 提示信息
     * @param {string} defaultValue 默认值
     * @returns {Promise} string: 用户输入的字符串
     */
    this.onRequestInputString = (message, defaultValue) => {
        return new Promise((resolve) => {
            resolve(defaultValue);
        });
    };

    /**
     * 请求单选
     * @param {string} message 提示信息
     * @param {Array} options 选项数组
     * @param {number} defaultSelectIndex 默认选中索引
     * @returns {Promise} number: 用户选择的索引
     */
    this.onRequestSingleSelect = (message, options, defaultSelectIndex) => {
        return new Promise((resolve) => {
            resolve(defaultSelectIndex);
        });
    };

    /**
     * 请求多选
     * @param {string} message 提示信息
     * @param {Array} options 选项数组
     * @param {Array} defaultSelectIndices 默认选中索引数组
     * @returns {Promise} number数组: 用户选择的索引数组
     */
    this.onRequestMultiSelect = (message, options, defaultSelectIndices) => {
        return new Promise((resolve) => {
            resolve(defaultSelectIndices);
        });
    };
}
export { appoxCallback };

/**
 * 在流程中使用的获取独立任务状态回调接口
 * @constructor
 */
function AppoxRunTaskCallback() {

    /**
     * 获取独立任务状态
     * @returns {TaskState} 任务状态
     */
    this.getTaskState = () => {
        return appoxEnums.TaskState.Unknown;
    };

    /**
     * 获取当前状态描述
     * @returns {string} 当前任务状态的描述
     */
    this.getTaskStateDescription = () => {
        return "";
    };

    /**
     * 获取任务进度，单位百分比
     * @returns {number} 任务进度
     */
    this.getTaskProgress = () => {
        return 0.0;
    };

    /**
     * 获取任务配置
     * @returns {string} 任务配置内容
     */
    this.getTaskConfig = () => {
        return "";
    };

    /**
     * 获取任务返回值
     * @returns {string} 任务返回值
     */
    this.getTaskReturnValue = () => {
        return "";
    };

    /**
     * 取消任务
     * @returns {Promise} boolean: 是否成功取消
     */
    this.cancelTask = () => {
        return new Promise((resolve) => {
            resolve(true);
        });
    };
}
export { AppoxRunTaskCallback };

/**
 * Session唯一标识符
 * @constructor
 * @param {string} sessionIdString Session字符串（格式：yyyy-MM-dd-HH-mm-ss）
 */
function AppoxSessionIdentifier(sessionIdString) {

    /**
     * 是否有效
     * @type {boolean}
     */
    this.isValid = false;
    
    this.year = 0;
    this.month = 0;
    this.day = 0;
    this.hour = 0;
    this.minute = 0;
    this.second = 0;

    if (sessionIdString != null && typeof sessionIdString == "string" && sessionIdString.length == 19) {
        this.year = Number(sessionIdString.slice(0, 4));
        this.month = Number(sessionIdString.slice(5, 7));
        this.day = Number(sessionIdString.slice(8, 10));
        this.hour = Number(sessionIdString.slice(11, 13));
        this.minute = Number(sessionIdString.slice(14, 16));
        this.second = Number(sessionIdString.slice(17, 19));
        this.isValid = true;
    }

    /**
     * 将Session唯一标识符转换为字符串
     * @returns {string} 格式为"yyyy-MM-dd-HH-mm-ss"的Session字符串
     */
    this.toString = () => {
        if (!this.isValid) return null;
        return String(this.year).padStart(4, '0') + "-" + String(this.month).padStart(2, '0') + "-" + String(this.day).padStart(2, '0') + "-" + String(this.hour).padStart(2, '0') + "-" + String(this.minute).padStart(2, '0') + "-" + String(this.second).padStart(2, '0');
    };

    /**
     * 将Session唯一标识符转换为Date对象
     * @returns {Date} 对应的Date对象
     */
    this.toDate = () => {
        if (!this.isValid) return null;
        return new Date(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, 0);
    }

    /**
     * 将Date对象转换为Session唯一标识符
     * @param {Date} date 日期对象
     */
    this.fromDate = (date) => {
        if (date == null) {
            this.isValid = false;
            return;
        }
        this.year = date.getFullYear();
        this.month = date.getMonth() + 1;
        this.day = date.getDate();
        this.hour = date.getHours();
        this.minute = date.getMinutes();
        this.second = date.getSeconds();
        this.isValid = true;
    }

    /**
     * 将bigint转换为Session唯一标识符
     * @param {bigint} num bigint对象
     */
    this.fromBitInt = (num) => {
        if (num == null || typeof num !== 'bigint') {
            this.isValid = false;
            return;
        }
        this.year = Number((num & 0xFFFF000000000000n) >> 48n);
        this.month = Number((num & 0x0000FF0000000000n) >> 40n);
        this.day = Number((num & 0x000000FF00000000n) >> 32n);
        this.hour = Number((num & 0x00000000FF0000n) >> 16n);
        this.minute = Number((num & 0x0000000000FF00n) >> 8n);
        this.second = Number(num & 0x000000000000FFn);
        this.isValid = true;
    }

    /**
     * 比较两个Session唯一标识符是否相等
     * @param {AppoxSessionIdentifier} other 另一个Session唯一标识符
     * @returns {boolean} 是否相等
     */
    this.equals = (other) => {
        if (!(other instanceof AppoxSessionIdentifier)) return false;
        if (!this.isValid || !other.isValid) return false;
        return this.year === other.year &&
               this.month === other.month &&
               this.day === other.day &&
               this.hour === other.hour &&
               this.minute === other.minute &&
               this.second === other.second;
    }
}
export { AppoxSessionIdentifier };

/**
 * 在session中的时间
 * @constructor
 * @param {AppoxSessionIdentifier} session 所在Session
 * @param {number} time 时间偏置，单位秒
 */
function AppoxTimeWithSession(session, time) {
    this.session = session;
    this.time = time;
}
export { AppoxTimeWithSession };

/**
 * 设备状态详情
 * @constructor
 * @param {GeneralDeviceStatus} status 设备状态
 * @param {string} description 状态描述
 */
function AppoxDeviceStatusDetail(status, description) {
    this.status = status;
    this.description = description;
}
export { AppoxDeviceStatusDetail };

/**
 * 音频驱动信息
 * @constructor
 * @param {string} driverID 驱动ID
 * @param {string} driverName 驱动名称
 */
function AppoxAudioDriverInfo(driverID, driverName) {
    this.driverID = driverID;
    this.driverName = driverName;
}
export { AppoxAudioDriverInfo };

/**
 * 音频设备信息
 * @constructor
 * @param {string} deviceID 设备ID
 * @param {string} deviceName 设备名称
 */
function AppoxAudioDeviceInfo(deviceID, deviceName) {
    this.deviceID = deviceID;
    this.deviceName = deviceName;
}
export { AppoxAudioDeviceInfo };

/**
 * 当前全局缓存数据在时间线上的范围
 * @constructor
 * @param {number} begin 缓存数据时间范围的下沿（最早），单位秒
 * @param {number} end 缓存数据时间范围的下沿（最迟），单位秒
 */
function AppoxBufferRange(begin, end) {
    this.begin = begin;
    this.end = end;
}
export { AppoxBufferRange };

/**
 * 总线通道信息
 * @constructor
 * @param {BusChannelType} type 通道类型
 */
function AppoxBusChannelInfo(type) {
    this.type = type;
}
export { AppoxBusChannelInfo };

/**
 * 总线设备ID
 * @constructor
 * @param {string} type 设备类型
 * @param {bigint} serial 序列号
 * @param {number} index 索引
 */
function AppoxBusDeviceID(type, serial, index) {
    this.type = type;
    this.serial = serial; // bigint
    this.index = index;
}
export { AppoxBusDeviceID };

/**
 * 总线设备信息
 * @constructor
 * @param {AppoxBusDeviceID} id 设备ID
 * @param {Array} supportedTypes BusChannelType数组: 支持的通道类型
 * @param {string} description 设备描述
 */
function AppoxBusDeviceInfo(id, supportedTypes, description) {
    this.id = id;
    this.supportedTypes = supportedTypes;
    this.description = description;
}
export { AppoxBusDeviceInfo };

/**
 * 总线报文信息
 * @constructor
 * @param {string} messageID 报文ID
 * @param {string} messageName 报文名称
 * @param {number} localID 通道内ID
 * @param {number} byteLength 字节数
 * @param {string} protocolFileID 协议名称（多通道的情况下包括通道名）
 */
function AppoxBusMessageInfo(messageID, messageName, localID, byteLength, protocolFileID) {
    this.messageID = messageID;
    this.messageName = messageName;
    this.localID = localID;
    this.byteLength = byteLength;
    this.protocolFileID = protocolFileID;
}
export { AppoxBusMessageInfo };

/**
 * 总线协议文件ID
 * @constructor
 * @param {string} fileName 协议文件名
 * @param {string} md5 文件MD5
 */
function AppoxBusProtocolFileID(fileName, md5) {
    this.fileName = fileName;
    this.md5 = md5;
}
export { AppoxBusProtocolFileID };

/**
 * 总线信号信息
 * @constructor
 * @param {string} signalID 信号ID
 * @param {string} signalName 信号名称
 * @param {string} unit 单位
 * @param {Object} enums 枚举值，键为枚举值(bigint)，值为枚举值描述(string)
 */
function AppoxBusSignalInfo(signalID, signalName, unit, enums) {
    this.signalID = signalID;
    this.signalName = signalName;
    this.unit = unit;
    this.enums = enums;
}
export { AppoxBusSignalInfo };

/**
 * Session无关时间信息
 * @constructor
 * @param {bigint} cpuTick 到达时CPU计数，0表示无效
 * @param {bigint} hostPosix 到达时主机Posix时间，单位纳秒，0表示无效
 * @param {bigint} guestPosix 采样时客机Posix时间，单位纳秒，0表示无效
 * @param {bigint} gnssPosix 采样时卫星Posix时间，单位纳秒，0表示无效
 */
function AppoxIndependentTimeInfo(cpuTick, hostPosix, guestPosix, gnssPosix) {
    this.cpuTick = cpuTick; // bigint
    this.hostPosix = hostPosix; // bigint
    this.guestPosix = guestPosix; // bigint
    this.gnssPosix = gnssPosix; // bigint
}
export { AppoxIndependentTimeInfo };

/**
 * 时间戳
 * @constructor
 * @param {AppoxSessionIdentifier} session Session标识符
 * @param {number} offset 时间偏置
 * @param {TimeOffsetSync} offsetSync 时间偏置同步状态
 * @param {AppoxIndependentTimeInfo} timeInfo Session无关时间信息
 */
function AppoxTimestamp(session, offset, offsetSync, timeInfo) {
    this.session = session;
    this.offset = offset;
    this.offsetSync = offsetSync;
    this.timeInfo = timeInfo;
}
export { AppoxTimestamp };

/**
 * 控制台组件信息
 * @constructor
 * @param {string} ownerPluginID 所属插件ID
 * @param {string} id 组件ID
 * @param {string} title 标题
 */
function AppoxConsoleClassInfo(ownerPluginID, id, title) {
    this.ownerPluginID = ownerPluginID;
    this.id = id;
    this.title = title;
}
export { AppoxConsoleClassInfo };

/**
 * CPU时间模型
 * @constructor
 * @param {bigint} startCPUTick Session开始时的CPU计数
 * @param {bigint} cpuTicksPerSecond 每秒增加的CPU计数
 */
function AppoxCPUTimeModel(startCPUTick, cpuTicksPerSecond) {
    this.startCPUTick = startCPUTick; // bigint
    this.cpuTicksPerSecond = cpuTicksPerSecond; // bigint
}
export { AppoxCPUTimeModel };

/**
 * 设备组件信息
 * @constructor
 * @param {string} ownerPluginID 所属插件ID
 * @param {string} id 组件ID
 * @param {string} title 标题
 */
function AppoxDeviceClassInfo(ownerPluginID, id, title) {
    this.ownerPluginID = ownerPluginID;
    this.id = id;
    this.title = title;
}
export { AppoxDeviceClassInfo };

/**
 * 事件基础信息
 * @constructor
 * @param {string} name 事件名称
 * @param {AppoxSessionIdentifier} session 所属session ID
 * @param {number} offset 在所属session中的时间偏置
 * @param {Date} timeStampLocal 事件发生时刻（本地时间，精确至毫秒）
 * @param {string} configID 事件触发条件ID
 * @param {string} description 事件触发条件描述
 * @param {string} eventHandle 事件对象
 */
function AppoxEventData(name, session, offset, timeStampLocal, configID, description, eventHandle) {
    this.name = name;
    this.session = session;
    this.offset = offset;
    this.timeStampLocal = timeStampLocal;
    this.configID = configID;
    this.description = description;
    this.eventHandle = eventHandle;
}
export { AppoxEventData };

/**
 * 事件完整信息
 * @constructor
 * @param {AppoxEventData} data 基础信息
 * @param {string} comment 注释
 * @param {EventSessionRecordStatus} sessionRecordStatus 事件session记录状态
 */
function AppoxEventInfo(data, comment, sessionRecordStatus) {
    this.data = data;
    this.comment = comment;
    this.sessionRecordStatus = sessionRecordStatus;
}
export { AppoxEventInfo };

/**
 * POSIX时间模型
 * @constructor
 * @param {bigint} startPosix Session开始时的Posix时间，单位毫秒，0表示无效
 * @param {number} timeRatio CPU时间转为Posix时间的时间比例，应为1.0左右
 */
function AppoxPosixTimeModel(startPosix, timeRatio) {
    this.startPosix = startPosix; // bigint
    this.timeRatio = timeRatio;
}
export { AppoxPosixTimeModel };

/**
 * GPU解码测试结果
 * @constructor
 * @param {Object} testDataAvailable 测试用数据是否可用，键为编码格式（如mjpeg, h264, h265等），值为是否可用（boolean）
 * @param {Object} testResults 测试结果，键为测试项(string)，值为每秒可处理像素数（bigint，null表示未测试，0表示失败）
 */
function AppoxGPUDecoderTestResults(testDataAvailable, testResults) {
    this.testDataAvailable = testDataAvailable;
    this.testResults = testResults; // string : bigint
}
export { AppoxGPUDecoderTestResults };

/**
 * 图表定义
 * @constructor
 * @param {GraphType} type 图表类型
 * @param {Array} config string数组: 图表定义相关参数
 * @param {string} mainTitle 图表标题
 * @param {Array} columnTitles string数组: 各列数据标题
 */
function AppoxGraphDefinition(type, config, mainTitle, columnTitles) {
    this.type = type;
    this.config = config;
    this.mainTitle = mainTitle;
    this.columnTitles = columnTitles;
}
export { AppoxGraphDefinition };

/**
 * 图表数据
 * @constructor
 * @param {number} id 图表ID
 * @param {AppoxGraphDefinition} definition 图表定义
 * @param {Array} params string数组: 图表定义相关参数
 * @param {number} dataDim0 数据维度0长度
 * @param {number} dataDim1 数据维度1长度
 * @param {Array} data number数组: 图表数据
 */
function AppoxGraphData(id, definition, params, dataDim0, dataDim1, data) {
    this.id = id;
    this.definition = definition;
    this.params = params;
    this.dataDim0 = dataDim0;
    this.dataDim1 = dataDim1;
    this.data = data;
}
export { AppoxGraphData };

/**
 * 显卡信息
 * @constructor
 * @param {number} vendor 厂商
 * @param {number} cardIndex 显卡序号
 * @param {bigint} memoryCapacity 显存总容量
 * @param {bigint} memoryFree 显存可用容量
 */
function AppoxGraphicCardInfo(vendor, cardIndex, memoryCapacity, memoryFree) {
    this.vendor = vendor;
    this.cardIndex = cardIndex;
    this.memoryCapacity = memoryCapacity; // bigint
    this.memoryFree = memoryFree; // bigint
}
export { AppoxGraphicCardInfo };

/**
 * 清单信息
 * @constructor
 * @param {LogLevel} type 清单信息级别
 * @param {string} text 清单信息文本
 * @param {Date} time 清单信息时间
 * @param {number} repeatedCount 清单信息重复次数
 */
function AppoxLogMessage(type, text, time, repeatedCount) {
    this.type = type;
    this.text = text;
    this.time = time;
    this.repeatedCount = repeatedCount;
}
export { AppoxLogMessage };

/**
 * 组件详情
 * @constructor
 * @param {Object} outputSamples 输出样本的通道ID和别名，键为通道ID(string)，值为别名(string)
 * @param {Array} outputScenes string数组: 输出场景的ID
 * @param {Object} outputSignals 输出信号类别和名称，键为类别(string，默认类别使用空字符串)，值为信号名称列表(string数组)
 * @param {Array} outputGraphs string数组: 输出图表的标题
 * @param {Array} requiredSignalPackings string数组: 所需信号打包数据通道ID
 * @param {Array} requiredVideoChannels UsingVideoChannel数组: 所需视频通道
 * @param {Array} requiredRecordDataTypes RecordDataType数组: 写入数据文件所需的数据类型
 */
function AppoxModuleDetails(outputSamples, outputScenes, outputSignals, outputGraphs, requiredSignalPackings, requiredVideoChannels, requiredRecordDataTypes) {
    this.outputSamples = outputSamples;
    this.outputScenes = outputScenes;
    this.outputSignals = outputSignals;
    this.outputGraphs = outputGraphs;
    this.requiredSignalPackings = requiredSignalPackings;
    this.requiredVideoChannels = requiredVideoChannels;
    this.requiredRecordDataTypes = requiredRecordDataTypes;
}
export { AppoxModuleDetails };

/**
 * 原生组件信息
 * @constructor
 * @param {string} ownerPluginID 所属插件ID
 * @param {string} id 组件ID
 * @param {string} title 原生组件名称
 * @param {string} nativeType 对应的原生插件类型ID
 * @param {Object} libraryVersions 绑定的各原生库版本，键为库类别(NativeLibraryType)，值为版本(string)
 * @param {Object} libraryDebugIDs 绑定的各原生库的调试编号，键为库类别(NativeLibraryType)，值为调试编号(number)
 */
function AppoxNativeClassInfo(ownerPluginID, id, title, nativeType, libraryVersions, libraryDebugIDs) {
    this.ownerPluginID = ownerPluginID;
    this.id = id;
    this.title = title;
    this.nativeType = nativeType;
    this.libraryVersions = libraryVersions;
    this.libraryDebugIDs = libraryDebugIDs;
}
export { AppoxNativeClassInfo };

/**
 * 插件包信息
 * @constructor
 * @param {string} id 插件包ID
 * @param {string} name 插件包名称
 * @param {string} version 版本
 * @param {string} brief 简介
 * @param {PluginPackStatus} status 状态
 * @param {PluginPackError} error 错误信息
 * @param {string} appLayerDetails 应用层详情
 * @param {string} nativeLayerDetails 原生层详情
 */
function AppoxPluginPackInfo(id, name, version, brief, status, error, appLayerDetails, nativeLayerDetails) {
    this.id = id;
    this.name = name;
    this.version = version;
    this.brief = brief;
    this.status = status;
    this.error = error;
    this.appLayerDetails = appLayerDetails;
    this.nativeLayerDetails = nativeLayerDetails;
}
export { AppoxPluginPackInfo };

/**
 * 摄像头信息
 * @constructor
 * @param {SpecialCameraType} specialCameraType 特殊摄像头类型
 * @param {string} metaProtocol Meta信息协议
 * @param {Array} meta number数组: Meta信息
 * @param {number} horizontalFov 标准针孔模型下的横向视场角（未去畸变时为空）
 */
function AppoxCameraInfo(specialCameraType, metaProtocol, meta, horizontalFov) {
    this.specialCameraType = specialCameraType;
    this.metaProtocol = metaProtocol;
    this.meta = meta;
    this.horizontalFov = horizontalFov;
}
export { AppoxCameraInfo };

/**
 * 数据处理组件信息
 * @constructor
 * @param {string} ownerPluginID 所属插件ID
 * @param {string} id 组件ID
 * @param {string} title 标题
 */
function AppoxProcessorClassInfo(ownerPluginID, id, title) {
    this.ownerPluginID = ownerPluginID;
    this.id = id;
    this.title = title;
}
export { AppoxProcessorClassInfo };

/**
 * Session筛选标志位
 * @constructor
 * @param {boolean} searchTrue 是否符合搜索条件
 * @param {boolean} checkTrue 是否已选中
 */
function AppoxSessionFilterFlags(searchTrue, checkTrue) {
    this.searchTrue = searchTrue;
    this.checkTrue = checkTrue;
}
export { AppoxSessionFilterFlags };

/**
 * 独立任务组件信息
 * @constructor
 * @param {string} ownerPluginID 所属插件ID
 * @param {string} id 组件ID
 * @param {string} title 标题
 * @param {string} defaultConfig 默认配置
 */
function AppoxTaskClassInfo(ownerPluginID, id, title, defaultConfig) {
    this.ownerPluginID = ownerPluginID;
    this.id = id;
    this.title = title;
    this.defaultConfig = defaultConfig;
}
export { AppoxTaskClassInfo };

/**
 * 信号树节点
 * @constructor
 * @param {SignalTreeNodeType} type 节点类别
 * @param {string} id 节点ID
 * @param {string} name 节点名称
 * @param {Array} children AppoxSignalTreeNode数组: 子节点
 */
function AppoxSignalTreeNode(type, id, name, children) {
    this.type = type;
    this.id = id;
    this.name = name;
    this.children = children;
}
export { AppoxSignalTreeNode };

/**
 * 浮点坐标点
 * @constructor
 * @param {number} x X坐标
 * @param {number} y Y坐标
 */
function AppoxFloatPoint(x, y) {
    this.x = x;
    this.y = y;
}
export { AppoxFloatPoint };

/**
 * 整型尺寸
 * @constructor
 * @param {number} width 宽度
 * @param {number} height 高度
 */
function AppoxIntSize(width, height) {
    this.width = width;
    this.height = height;
}
export { AppoxIntSize };

/**
 * 视频输入模式
 * @constructor
 * @param {VideoDataCodec} inputCodec 输入编码格式
 * @param {AppoxIntSize} size 视频尺寸 (分辨率)
 */
function AppoxVideoInputMode(inputCodec, size) {
    this.inputCodec = inputCodec;
    this.size = size;
}
export { AppoxVideoInputMode };

/**
 * 视频通道信息
 * @constructor
 * @param {AppoxVideoInputMode} inputMode 输入模式
 * @param {number} recordFPS 采集文件帧率
 * @param {boolean} recordFPSAlign 是否按采集文件帧率对齐
 */
function AppoxVideoChannelInfo(inputMode, recordFPS, recordFPSAlign) {
    this.inputMode = inputMode;
    this.recordFPS = recordFPS;
    this.recordFPSAlign = recordFPSAlign;
}
export { AppoxVideoChannelInfo };

/**
 * 视频设备ID
 * @constructor
 * @param {string} type 设备类型
 * @param {string} localID 本地ID
 */
function AppoxVideoDeviceID(type, localID) {
    this.type = type;
    this.localID = localID;
}
export { AppoxVideoDeviceID };

/**
 * 视频设备信息
 * @constructor
 * @param {AppoxVideoDeviceID} id 设备ID
 * @param {string} hardwareInfo 设备信息描述
 * @param {Array} inputModes AppoxVideoInputMode数组: 输入模式
 * @param {Array} outputModes AppoxVideoOutputMode数组: 输出模式
 */
function AppoxVideoDeviceInfo(id, hardwareInfo, inputModes, outputModes) {
    this.id = id;
    this.hardwareInfo = hardwareInfo;
    this.inputModes = inputModes;
    this.outputModes = outputModes;
}
export { AppoxVideoDeviceInfo };

/**
 * 视频输出模式
 * @constructor
 * @param {VideoDataCodec} outputCodec 视频输出编码格式
 * @param {AppoxIntSize} size 视频尺寸 (分辨率)
 */
function AppoxVideoOutputMode(outputCodec, size) {
    this.outputCodec = outputCodec;
    this.size = size;
}
export { AppoxVideoOutputMode };

/**
 * 通用图像数据
 * @constructor
 * @param {number} width 宽度
 * @param {number} height 高度
 * @param {boolean} withAlpha 是否有Alpha通道
 * @param {boolean} bgrInverted BGR是否逆序
 * @param {number} rowBytes 每行数据字节数
 * @param {Uint8Array} data 图像数据，每个像素的存放顺序为BGR或BGRA（若BGR逆序则为RGB或RGBA）
 */
function AppoxCommonImage(width, height, withAlpha, bgrInverted, rowBytes, data) {
    this.width = width;
    this.height = height;
    this.withAlpha = withAlpha;
    this.bgrInverted = bgrInverted;
    this.rowBytes = rowBytes;
    this.data = data; // Uint8Array
}
export { AppoxCommonImage };

/**
 * 整型矩形
 * @constructor
 * @param {number} x 左上角X坐标
 * @param {number} y 左上角Y坐标
 * @param {number} width 宽度
 * @param {number} height 高度
 */
function AppoxIntRect(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
}
export { AppoxIntRect };

/**
 * 单个总线报文样本
 * @constructor
 * @param {AppoxTimestamp} timestamp 时间戳
 * @param {number} timeline 时间线，单位秒
 * @param {number} channel 接收来源通道或发送目标通道（1~48）
 * @param {number} length 总线报文的实际长度
 * @param {number} id 报文ID
 * @param {Uint8Array} data 报文数据。可能小于报文的实际长度（出于内存用量控制等目的）
 * @param {BusChannelType} type 总线通道类型
 * @param {BusRawDataState} state 总线数据的(发送)状态
 * @param {bigint} schedulePosixTime 预约发送的授时服务器时间，单位纳秒，0表示无效
 */
function AppoxBusMessageSample(timestamp, timeline, channel, length, id, data, type, state, schedulePosixTime) {
    this.timestamp = timestamp;
    this.timeline = timeline;
    this.channel = channel;
    this.length = length;
    this.id = id;
    this.data = data; // Uint8Array
    this.type = type;
    this.state = state;
    this.schedulePosixTime = schedulePosixTime;
}
export { AppoxBusMessageSample };

/**
 * 解析总线报文得到的信号值及相关信息
 * @constructor
 * @param {string} signalName 信号名
 * @param {number} value 信号值
 * @param {string} unit 单位
 * @param {string} enumValue 对应的枚举值（若存在枚举信息）
 */
function AppoxBusSignalValue(signalName, value, unit, enumValue) {
    this.signalName = signalName;
    this.value = value;
    this.unit = unit;
    this.enumValue = enumValue;
}
export { AppoxBusSignalValue };

/**
 * 用于发送的总线报文数据
 * @constructor
 * @param {number} channel 总线设备通道序号（1~48），报文将在对应硬件上发送
 * @param {number} id 报文ID
 * @param {Uint8Array} data 数据
 * @param {number} millisecondInterval 报文发送周期，单位毫秒（至少为10），若设为null则只发送一次
 */
function AppoxBusMessage(channel, id, data, millisecondInterval) {
    this.channel = channel;
    this.id = id;
    this.data = data; // Uint8Array
    this.millisecondInterval = millisecondInterval;
}
export { AppoxBusMessage };

/**
 * 数据引用计数信息
 * @constructor
 * @param {boolean} alwaysTransferBusMessage 总线报文是否无条件传输
 * @param {Array} busMessageReferences number数组: 各通道总线报文数据引用计数
 * @param {boolean} alwaysTransferPointCloud 点云是否无条件传输
 * @param {Array} pointCloudReferences number数组: 各通道点云数据引用计数
 * @param {boolean} alwaysTransferProcessorVideo 用于处理的视频数据是否无条件传输 (仅主机端有效)
 * @param {Array} processorVideoReferences number数组: 各通道视频数据(用于处理)引用计数 (仅主机端有效)
 * @param {boolean} alwaysTransferSignal 信号是否无条件传输
 * @param {Object} signalReferences 各通道信号数据引用计数，键为信号ID(string)，值为引用计数(number)
 * @param {Object} subscriberReferences 被订阅数据的引用计数，键为数据ID(string)，值为引用计数(number)
 */
function AppoxDataReferenceInfo(alwaysTransferBusMessage, busMessageReferences, alwaysTransferPointCloud, pointCloudReferences, alwaysTransferProcessorVideo, processorVideoReferences, alwaysTransferSignal, signalReferences, subscriberReferences) {
    this.alwaysTransferBusMessage = alwaysTransferBusMessage;
    this.busMessageReferences = busMessageReferences;
    this.alwaysTransferPointCloud = alwaysTransferPointCloud;
    this.pointCloudReferences = pointCloudReferences;
    this.alwaysTransferProcessorVideo = alwaysTransferProcessorVideo;
    this.processorVideoReferences = processorVideoReferences;
    this.alwaysTransferSignal = alwaysTransferSignal;
    this.signalReferences = signalReferences;
    this.subscriberReferences = subscriberReferences;
}
export { AppoxDataReferenceInfo };

/**
 * 数据订阅对象
 * @constructor
 * @param {string} guid 订阅唯一标识
 */
function AppoxDataSubscriber(guid) {

    /**
     * 是否已关闭
     * @type {boolean}
     */
    this.closed = false;
    
    this.guid = guid;
    this.stream = [];
    this.lastRequestTime = Date.now();
    this.lastDequeueTime = Date.now();
    
    /**
     * 从缓存取出所有新数据（需要确保经常调用，超时后将自动关闭订阅）
     * @returns {Array} Uint8Array数组: 数据流
     */
    this.dequeue = () => {
        if (!this.closed && appoxi7.isConnected() && Date.now() - this.lastDequeueTime > appoxi7.SubscriberDequeueMinInterval) {
            let packet = appoxi7.generateSubscriberRequestPacket(this.guid, false, false);
            let rawPacket = appoxi7.generateRawPacket(packet);
            if (appoxi7.isConnected()) appoxi7.socket.send(rawPacket);
            this.lastRequestTime = this.lastDequeueTime = Date.now();
        }
        let output = this.stream;
        this.stream = [];
        return output;
    };

    /**
     * 立即关闭订阅
     */
    this.close = () => {
        if (this.closed) return;
        let packet = appoxi7.generateSubscriberRequestPacket(this.guid, true, false);
        let rawPacket = appoxi7.generateRawPacket(packet);
        if (appoxi7.isConnected()) appoxi7.socket.send(rawPacket);
        this.closed = true;
    };

    this.idleRequest = () => {
        if (this.closed) return;
        let now = Date.now();
        if (now < this.lastRequestTime) this.lastRequestTime = now;
        if (now < this.lastDequeueTime) this.lastDequeueTime = now;
        if (now - this.lastRequestTime > appoxi7.SubscriberIdleRequestInterval) {
            this.lastRequestTime = now;
            let packet = appoxi7.generateSubscriberRequestPacket(this.guid, false, now - this.lastDequeueTime > appoxi7.SubscriberDequeueTimeout);
            let rawPacket = appoxi7.generateRawPacket(packet);
            if (appoxi7.isConnected()) appoxi7.socket.send(rawPacket);
        }
    };

    this.onResponse = (isClosed, dataArray) => {
        if (isClosed) this.closed = true;
        dataArray.forEach((data) => {
            this.stream.push(data);
        });
    };
}
export { AppoxDataSubscriber };

/**
 * 通道数据的秒侧写样本
 * @constructor
 * @param {AppoxTimestamp} timestamp 时间戳
 * @param {number} timeline 时间线，单位秒
 * @param {string} channelID 数据通道ID，如：总线通道bus@1, 视频通道video@0, 音频通道audio, 样本通道xxx-v1@0, 点云通道point-cloud@0等
 * @param {number} second 侧写所在秒，对应[N, N+1)内的数据，侧写样本的时间偏置为N+0.5
 * @param {number} sourceCount 数据源处的帧数
 * @param {number} availableCount 当前端可用帧数
 * @param {Array} offsets number数组: 通道数据在该时间范围内的所有时间偏置，可为null
 */
function AppoxSecondProfileSample(timestamp, timeline, channelID, second, sourceCount, availableCount, offsets) {
    this.timestamp = timestamp;
    this.timeline = timeline;
    this.channelID = channelID;
    this.second = second;
    this.sourceCount = sourceCount;
    this.availableCount = availableCount;
    this.offsets = offsets;
}
export { AppoxSecondProfileSample };

/**
 * 一秒内的所有侧写样本集合
 * @constructor
 * @param {AppoxSessionIdentifier} session 所在session
 * @param {number} second 侧写所在秒，对应[N, N+1)内的数据
 * @param {Object} profiles 样本表，键为数据通道ID(string)，值为AppoxSecondProfileSample
 */
function AppoxSecondProfilePack(session, second, profiles) {
    this.session = session;
    this.second = second;
    this.profiles = profiles;
}
export { AppoxSecondProfilePack };

/**
 * 手动触发器样本
 * @constructor
 * @param {AppoxTimestamp} timestamp 时间戳
 * @param {number} timeline 时间线，单位秒
 * @param {Array} states boolean数组: 手动触发器状态列表，长度16
 */
function AppoxManualTriggerSample(timestamp, timeline, states) {
    this.timestamp = timestamp;
    this.timeline = timeline;
    this.states = states;
}
export { AppoxManualTriggerSample };

/**
 * 通用样本
 * @constructor
 * @param {AppoxTimestamp} timestamp 时间戳
 * @param {number} timeline 时间线，单位秒
 * @param {string} protocol 样本协议
 * @param {number} channel 样本所属通道，null表示无通道信息（唯一通道样本）
 * @param {Array} values 值列表，元素可谓null、number或string
 * @param {number} numberOfSignificants 值列表中关键数值的个数（一般用于采集，非关键的将不保存）
 */
function AppoxGeneralSample(timestamp, timeline, protocol, channel, values, numberOfSignificants) {
    this.timestamp = timestamp;
    this.session = timestamp.session; // 兼容特化样本converters
    this.time = timestamp.offset; // 兼容特化样本converters
    this.timeline = timeline;
    this.protocol = protocol;
    this.channel = channel;
    this.fullProtocol = protocol + (channel == null ? "" : "@" + channel);
    this.values = values;
    this.numberOfSignificants = numberOfSignificants;
}
export { AppoxGeneralSample };

/**
 * 样本对
 * @constructor
 * @param {Object} sample1 样本1
 * @param {number} weight1 权重1
 * @param {number} index1 在样本缓存中的序号1
 * @param {Object} sample2 样本2
 * @param {number} weight2 权重2
 * @param {number} index2 在样本缓存中的序号2
 */
function AppoxSamplePair(sample1, weight1, index1, sample2, weight2, index2) {
    this.sample1 = sample1;
    this.weight1 = weight1;
    this.index1 = index1;
    this.sample2 = sample2;
    this.weight2 = weight2;
    this.index2 = index2;
}
export { AppoxSamplePair };

/**
 * 单个信号数据
 * @constructor
 * @param {number} offset 时间偏置，单位秒
 * @param {TimeOffsetSync} sync 时间偏置同步状态
 * @param {number} value 信号值
 */
function AppoxSignal(offset, sync, value) {
    this.offset = offset;
    this.sync = sync;
    this.value = value;
}
export { AppoxSignal };

/**
 * 信号样本
 * @constructor
 * @param {AppoxTimestamp} timestamp 时间戳
 * @param {number} timeline 时间线，单位秒
 * @param {number} value 信号值
 */
function AppoxSignalSample(timestamp, timeline, value) {
    this.timestamp = timestamp;
    this.timeline = timeline;
    this.value = value;
}
export { AppoxSignalSample };

/**
 * 输入的信号数据列表（单个信号类别）
 * @constructor
 * @param {AppoxSessionIdentifier} session 所属session ID
 * @param {string} signalID 信号的全局唯一ID
 * @param {Array} signals AppoxSignal数组: 信号列表
 */
function AppoxSignalsData(session, signalID, signals) {

    this.session = session;
    this.signalID = signalID;
    this.signals = signals;

    /**
     * 转换为信号样本列表
     * @returns {Array} AppoxSignalSample数组: 信号样本列表，若转换失败则返回null
     */
    this.toSamples = function() {
        let sessionTimeline = appoxUtility.getSessionTimelineLocal(this.session);
        if (sessionTimeline == null) return null;
        return this.signals.map(signal => new AppoxSignalSample(new AppoxTimestamp(this.session, signal.offset, signal.sync, null), sessionTimeline + signal.offset, signal.value));
    }
}
export { AppoxSignalsData };

/**
 * 音频帧样本，44100Hz，16bit，单通道
 * @constructor
 * @param {AppoxTimestamp} timestamp 时间戳
 * @param {number} timeline 时间线，单位秒
 * @param {Int16Array} data 音频数据
 */
function AppoxAudioSample(timestamp, timeline, data) {
    this.timestamp = timestamp;
    this.timeline = timeline;
    this.data = data; // Int16Array
}
export { AppoxAudioSample };

/**
 * 点云样本
 * @constructor
 * @param {AppoxTimestamp} timestamp 时间戳
 * @param {number} timeline 时间线，单位秒
 * @param {number} channel 样本通道（0~11）
 * @param {number} pointCount 可用的点个数
 * @param {number} sourcePointCount 数据源处的点个数
 * @param {Int16Array} pointPositions 各点坐标，单位厘米
 * @param {Uint8Array} pointIntensities 各点强度（0~255）
 */
function AppoxPointCloudSample(timestamp, timeline, channel, pointCount, sourcePointCount, pointPositions, pointIntensities) {
    this.timestamp = timestamp;
    this.timeline = timeline;
    this.channel = channel;
    this.pointCount = pointCount;
    this.sourcePointCount = sourcePointCount;
    this.pointPositions = pointPositions; // Int16Array[pointCount * 3]
    this.pointIntensities = pointIntensities; // Uint8Array[pointCount]
}
export { AppoxPointCloudSample };

/**
 * 确保相同目标的任务依次执行
 * @constructor
 */
function AppoxTaskBeat() {
    
    this.running = false;

    /**
     * 循环调用此方法
     * @param {Function} task 任务函数
     */
    this.handle = async (task) => {
        if (this.running) return;
        this.running = true;
        await task();
        this.running = false;
    }
}
export { AppoxTaskBeat };

/**
 * 数据流传输统计信息
 * @constructor
 * @param {bigint} successBytesPerSecond 每秒成功传输字节数
 * @param {bigint} failBytesPerSecond 每秒失败传输字节数
 */
function AppoxTransferStatistics(successBytesPerSecond, failBytesPerSecond) {
    this.successBytesPerSecond = successBytesPerSecond; // bigint
    this.failBytesPerSecond = failBytesPerSecond; // bigint
}
export { AppoxTransferStatistics };

/**
 * 内容传输进度详情
 * @constructor
 * @param {number} slice 切片进度，0～100%
 * @param {number} transfer 传输进度，0～100%
 * @param {number} restore 复原进度，0～100%
 */
function AppoxContentTransferProgressDetails(slice, transfer, restore) {
    this.slice = slice;
    this.transfer = transfer;
    this.restore = restore;
}
export { AppoxContentTransferProgressDetails };

/**
 * 内容传输任务状态
 * @constructor
 * @param {Date} commitTime 提交时间
 * @param {string} guid 任务唯一标识
 * @param {string} contentID 内容ID
 * @param {string} brief 任务简述
 * @param {number} payloadByteCount 有效载荷字节数
 * @param {AppoxContentTransferProgressDetails} progressDetails 进度详情
 */
function AppoxContentTransferTask(commitTime, guid, contentID, brief, payloadByteCount, progressDetails) {

    this.commitTime = commitTime;
    this.guid = guid;
    this.contentID = contentID;
    this.brief = brief;
    this.payloadByteCount = payloadByteCount;
    this.progressDetails = progressDetails;

    /**
     * 获取总进度
     * @returns {number} 总进度，0～100%
     */
    this.totalProgress = () => {
        if (this.progressDetails == null) return 0;
        else return this.progressDetails.slice * 0.25 + this.progressDetails.transfer * 0.5 + this.progressDetails.restore * 0.25;
    }
}
export { AppoxContentTransferTask };

/**
 * 内容传输状态总览
 * @constructor
 * @param {AppoxContentTransferTask[]} downloadTasks 下载任务列表
 * @param {AppoxContentTransferTask[]} uploadTasks 上传任务列表
 */
function AppoxContentTransferState(downloadTasks, uploadTasks) {
    this.downloadTasks = downloadTasks;
    this.uploadTasks = uploadTasks;
}
export { AppoxContentTransferState };

/**
 * 单值数据
 * @constructor
 * @param {Object} source 数据源
 */
function AppoxSingleValueData(source) {

    this.id = source.id;
    this.definition = source.definition;
    this.params = source.params;
    this.dataDim0 = source.dataDim0;
    this.dataDim1 = source.dataDim1;
    this.data = source.data;

    /**
     * 是否存在数据
     * @returns {boolean} 是否存在数据
     */
    this.hasData = function() {
        return this.data && this.data.length !== 0 && !isNaN(this.data[0]);
    };

    /**
     * 获取值
     * @returns {number} 值
     */
    this.getValue = function() {
        return this.data[0];
    };
}
export { AppoxSingleValueData };

/**
 * 散点图单轴范围
 * @constructor
 * @param {number} lower 下限
 * @param {number} upper 上限
 */
function AppoxScatterRange(lower, upper) {
    this.lower = lower;
    this.upper = upper;
}
export { AppoxScatterRange };

/**
 * 散点图数据
 * @constructor
 * @param {Object} source 数据源
 */
function AppoxScatterPointsData(source) {

    this.id = source.id;
    this.definition = source.definition;
    this.params = source.params;
    this.dataDim0 = source.dataDim0;
    this.dataDim1 = source.dataDim1;
    this.data = source.data;

    /**
     * 获取X轴标题
     * @returns {string} X轴标题
     */
    this.getXTitle = function() {
        return this.definition && this.definition.columnTitles && this.definition.columnTitles.length > 0 ? this.definition.columnTitles[0] : "";
    };

    /**
     * 获取Y轴标题
     * @returns {string} Y轴标题
     */
    this.getYTitle = function() {
        return this.definition && this.definition.columnTitles && this.definition.columnTitles.length > 1 ? this.definition.columnTitles[1] : "";
    };

    /**
     * 获取X轴范围
     * @returns {AppoxScatterRange} 范围对象
     */
    this.getXRange = function() {
        let lower = 0, upper = 0;
        try {
            lower = parseFloat(this.definition.config[0]);
            upper = parseFloat(this.definition.config[1]);
        } catch (e) {}
        return new AppoxScatterRange(lower, upper);
    };

    /**
     * 获取Y轴范围
     * @returns {AppoxScatterRange} 范围对象
     */
    this.getYRange = function() {
        let lower = 0, upper = 0;
        try {
            lower = parseFloat(this.definition.config[2]);
            upper = parseFloat(this.definition.config[3]);
        } catch (e) {}
        return new AppoxScatterRange(lower, upper);
    };

    /**
     * 获取x轴柱状图区间个数
     * @returns {number} x轴柱状图区间个数
     */
    this.getXHistCount = function() {
        let xHistCount = null;
        if (this.definition.config.length >= 6) {
            let val = parseInt(this.definition.config[4]);
            if (!isNaN(val) && val > 0) xHistCount = val;
        }
        if (xHistCount == null) {
            let xRange = this.getXRange();
            let range = xRange.upper - xRange.lower;
            for (let i = 20; i >= 5; i--) {
                let step = range / i;
                let logVal = Math.log10(step);
                if (logVal === Math.floor(logVal)) { xHistCount = i; break; }
                logVal = Math.log10(step * 0.5);
                if (logVal === Math.floor(logVal)) { xHistCount = i; break; }
                logVal = Math.log10(step * 0.2);
                if (logVal === Math.floor(logVal)) { xHistCount = i; break; }
            }
        }
        if (xHistCount == null) xHistCount = 5;
        return xHistCount;
    };

    /**
     * 获取y轴柱状图区间个数
     * @returns {number} y轴柱状图区间个数
     */
    this.getYHistCount = function() {
        let yHistCount = null;
        if (this.definition.config.length >= 6) {
            let val = parseInt(this.definition.config[5]);
            if (!isNaN(val) && val > 0) yHistCount = val;
        }
        if (yHistCount == null) {
            let yRange = this.getYRange();
            let range = yRange.upper - yRange.lower;
            for (let i = 20; i >= 5; i--) {
                let step = range / i;
                let logVal = Math.log10(step);
                if (logVal === Math.floor(logVal)) { yHistCount = i; break; }
                logVal = Math.log10(step * 0.5);
                if (logVal === Math.floor(logVal)) { yHistCount = i; break; }
                logVal = Math.log10(step * 0.2);
                if (logVal === Math.floor(logVal)) { yHistCount = i; break; }
            }
        }
        if (yHistCount == null) yHistCount = 5;
        return yHistCount;
    };

    /**
     * 是否存在数据
     * @returns {boolean} 是否存在数据
     */
    this.hasData = function() {
        if (!this.params || this.params.length === 0) return false;
        let count = parseFloat(this.params[0]);
        return !isNaN(count) && count > 0;
    };

    /**
     * 获取所有散点样本
     * @returns {Array} AppoxFloatPoint数组: 点样本数组
     */
    this.getPoints = function() {
        let count = 0;
        if (!this.params || this.params.length === 0) return [];
        count = parseInt(this.params[0]);
        if (isNaN(count) || count <= 0) return [];
        let step = this.dataDim1;
        let pts = [];
        for (let i = 0; i < count; i++) {
            pts.push(new AppoxFloatPoint(this.data[i * step], this.data[i * step + 1]));
        }
        return pts;
    };

    /**
     * 获取x轴柱状图各区间散点数
     * @returns {Array} number数组: 各区间散点数数组
     */
    this.getXHistValues = function() {
        let xRange = this.getXRange();
        let xCount = this.getXHistCount();
        let stepInv = xCount / (xRange.upper - xRange.lower);
        let pts = this.getPoints();
        let output = new Array(xCount).fill(0);
        for (let i = 0; i < pts.length; i++) {
            let target = (pts[i].x - xRange.lower) * stepInv;
            if (target < 0 || target > xCount) continue;
            let targetI = Math.min(xCount - 1, Math.floor(target));
            output[targetI]++;
        }
        return output;
    };

    /**
     * 获取y轴柱状图各区间散点数
     * @returns {Array} number数组: 各区间散点数数组
     */
    this.getYHistValues = function() {
        let yRange = this.getYRange();
        let yCount = this.getYHistCount();
        let stepInv = yCount / (yRange.upper - yRange.lower);
        let pts = this.getPoints();
        let output = new Array(yCount).fill(0);
        for (let i = 0; i < pts.length; i++) {
            let target = (pts[i].y - yRange.lower) * stepInv;
            if (target < 0 || target > yCount) continue;
            let targetI = Math.min(yCount - 1, Math.floor(target));
            output[targetI]++;
        }
        return output;
    };
}
export { AppoxScatterPointsData };

/**
 * 直方折线图X轴数值描述
 * @constructor
 * @param {number} base 基数
 * @param {number} step 步长
 * @param {number} count 数量
 */
function AppoxHistLineXValues(base, step, count) {
    this.base = base;
    this.step = step;
    this.count = count;
}
export { AppoxHistLineXValues };

/**
 * 直方折线图中的样本
 * @constructor
 * @param {string} name 名称
 * @param {number} histValue 柱状图值
 * @param {number} lineValue 折线图值，可为null
 */
function AppoxHistLineSample(name, histValue, lineValue) {
    this.name = name;
    this.histValue = histValue;
    this.lineValue = lineValue;
}
export { AppoxHistLineSample };

/**
 * 直方折线图数据
 * @constructor
 * @param {Object} source 数据源
 */
function AppoxHistAndLineData(source) {

    this.id = source.id;
    this.definition = source.definition;
    this.params = source.params;
    this.dataDim0 = source.dataDim0;
    this.dataDim1 = source.dataDim1;
    this.data = source.data;

    /**
     * 获取图表统计模式
     * @returns {HistLineMode} 统计模式
     */
    this.getMode = function() {
        return this.definition && this.definition.config && this.definition.config.length > 0 ? this.definition.config[0] : null;
    };

    /**
     * 获取X轴标题
     * @returns {string} X轴标题
     */
    this.getXTitle = function() {
        return this.definition && this.definition.config && this.definition.config.length > 1 ? this.definition.config[1] : "";
    };

    /**
     * 获取柱状图标题
     * @returns {string} 柱状图标题
     */
    this.getHistTitle = function() {
        return this.definition && this.definition.columnTitles && this.definition.columnTitles.length > 0 ? this.definition.columnTitles[0] : "";
    };

    /**
     * 获取折线图标题
     * @returns {string} 折线图标题
     */
    this.getLineTitle = function() {
        return this.isLineEnabled() && this.definition.columnTitles.length > 2 ? this.definition.columnTitles[2] : null;
    };

    /**
     * 获取折线图是否启用
     * @returns {boolean} 折线图是否启用
     */
    this.isLineEnabled = function() {
        return this.definition && this.definition.columnTitles && this.definition.columnTitles.length >= 4;
    };

    /**
     * 获取X轴的值或标签
     * @returns {Object} AppoxHistLineXValues或string数组: X轴的值或标签对象
     */
    this.getXValuesOrLabels = function() {
        let config = this.definition.config;
        if (config[4] === "XValues") {
            let base = parseFloat(config[5]);
            let step = parseFloat(config[6]);
            let count = parseInt(config[7]);
            if (!isNaN(base) && !isNaN(step) && !isNaN(count)) {
                return new AppoxHistLineXValues(base, step, count);
            } else {
                return null;
            }
        } else {
            let labels = [];
            for (let i = 5; i < config.length; i++) {
                labels.push(config[i]);
            }
            return labels;
        }
    };

    /**
     * 检查是否存在数据
     * @returns {boolean} 是否存在数据
     */
    this.hasData = function() {
        if (!this.data || this.data.length === 0) return false;
        let config = this.definition.config;
        let lineEnabled = this.isLineEnabled();
        let step = this.dataDim1;
        let rows = this.dataDim0;
        let defaultHistValue = 0, defaultLineValue = 0;
        try {
            defaultHistValue = parseFloat(config[2]);
            defaultLineValue = parseFloat(config[3]);
        } catch (e) {}
        for (let i = 0; i < rows; i++) {
            let offset = i * step;
            if (this.data[offset] !== defaultHistValue) return true;
            if (this.data[offset + 1] !== 0) return true;
            if (lineEnabled) {
                if (this.data[offset + 2] !== defaultLineValue) return true;
                if (this.data[offset + 3] !== 0) return true;
            }
        }
        return false;
    };

    /**
     * 获取统计数据样本
     * @returns {Array} AppoxHistLineSample数组: 统计数据样本数组
     */
    this.getSamples = function() {
        let config = this.definition.config;
        let lineEnabled = this.isLineEnabled();
        let step = this.dataDim1;
        let rows = this.dataDim0;
        let samples = [];
        for (let i = 0; i < rows; i++) {
            samples.push(new AppoxHistLineSample("", 0, null));
        }
        if (config[4] === "XValues") {
            for (let i = 0; i < samples.length; i++) {
                samples[i].name = config[5 + i];
            }
        } else {
            let baseDouble = 0, stepDouble = 0;
            try {
                baseDouble = parseFloat(config[5]);
                stepDouble = parseFloat(config[6]);
            } catch (e) {}
            for (let i = 0; i < samples.length; i++) {
                let lower = baseDouble + i * stepDouble;
                let upper = baseDouble + (i + 1) * stepDouble;
                samples[i].name = lower + "~" + upper;
            }
        }
        let k = 1;
        if (config[0] === "Percentage") {
            let sum = 0;
            for (let i = 0; i < samples.length; i++) {
                sum += this.data[i * step];
            }
            k = sum === 0 ? 0 : (100.0 / sum);
        }
        for (let i = 0; i < samples.length; i++) {
            samples[i].histValue = this.data[i * step] * k;
            if (lineEnabled) samples[i].lineValue = this.data[i * step + 2];
        }
        return samples;
    };
}
export { AppoxHistAndLineData };

/**
 * 矩阵表单轴范围
 * @constructor
 * @param {number} base 起始值
 * @param {number} step 步长
 * @param {number} count 区间个数
 */
function AppoxMatrixTableRange(base, step, count) {
    this.base = base;
    this.step = step;
    this.count = count;
}
export { AppoxMatrixTableRange };

/**
 * 矩阵表中数值的范围
 * @constructor
 * @param {number} lower 下限值
 * @param {number} upper 上限值
 */
function AppoxMatrixTableValueRefRange(lower, upper) {
    this.lower = lower;
    this.upper = upper;
}
export { AppoxMatrixTableValueRefRange };

/**
 * 矩阵表数据
 * @constructor
 * @param {Object} source 数据源
 */
function AppoxMatrixTableData(source) {
    
    this.id = source.id;
    this.definition = source.definition;
    this.params = source.params;
    this.dataDim0 = source.dataDim0;
    this.dataDim1 = source.dataDim1;
    this.data = source.data;

    /**
     * 获取矩阵表统计模式
     * @returns {MatrixTableMode} 统计模式
     */
    this.getMode = function() {
        return this.definition && this.definition.config && this.definition.config.length > 0 ? this.definition.config[0] : null;
    };

    /**
     * 获取X轴标题
     * @returns {string} X轴标题
     */
    this.getXTitle = function() {
        return this.definition && this.definition.config && this.definition.config.length > 9 ? this.definition.config[9] : "";
    };

    /**
     * 获取Y轴标题
     * @returns {string} Y轴标题
     */
    this.getYTitle = function() {
        return this.definition && this.definition.config && this.definition.config.length > 10 ? this.definition.config[10] : "";
    };

    /**
     * 获取X轴范围
     * @returns {AppoxMatrixTableRange} X轴范围
     */
    this.getXRange = function() {
        try {
            let base = parseFloat(this.definition.config[1]);
            let step = parseFloat(this.definition.config[2]);
            let count = parseInt(this.definition.config[3]);
            if (!isNaN(base) && !isNaN(step) && !isNaN(count)) {
                return new AppoxMatrixTableRange(base, step, count);
            }
        } catch (e) {}
        return null;
    };

    /**
     * 获取Y轴范围
     * @returns {AppoxMatrixTableRange} Y轴范围
     */
    this.getYRange = function() {
        try {
            let base = parseFloat(this.definition.config[4]);
            let step = parseFloat(this.definition.config[5]);
            let count = parseInt(this.definition.config[6]);
            if (!isNaN(base) && !isNaN(step) && !isNaN(count)) {
                return new AppoxMatrixTableRange(base, step, count);
            }
        } catch (e) {}
        return null;
    };

    /**
     * 获取矩阵表数据参考范围
     * @returns {AppoxMatrixTableValueRefRange} 数据参考范围
     */
    this.getValueRefRange = function() {
        try {
            let lower = parseFloat(this.definition.config[7]);
            let upper = parseFloat(this.definition.config[8]);
            if (!isNaN(lower) && !isNaN(upper)) {
                return new AppoxMatrixTableValueRefRange(lower, upper);
            }
        } catch (e) {}
        return null;
    };

    /**
     * 检查是否存在数据
     * @returns {boolean} 是否存在数据
     */
    this.hasData = function() {
        if (!this.data || this.data.length === 0) return false;
        let xr = this.getXRange();
        let yr = this.getYRange();
        if (!xr || !yr) return false;
        let xCount = xr.count;
        let yCount = yr.count;
        let defaultValue = 0;
        try {
            defaultValue = parseFloat(this.definition.config[11]);
        } catch (e) {}
        let step = this.dataDim1;
        for (let x = 0; x < xCount; x++) {
            for (let y = 0; y < yCount; y++) {
                if (this.data[x * step + y * 2] !== defaultValue) return true;
                if (this.data[x * step + y * 2 + 1] !== 0) return true;
            }
        }
        return false;
    };

    /**
     * 获取统计数据
     * @returns {Array} 统计数据二维数组
     */
    this.getValues = function() {
        let xr = this.getXRange();
        let yr = this.getYRange();
        if (!xr || !yr) return [];
        let xCount = xr.count;
        let yCount = yr.count;
        let k = 1;
        let step = this.dataDim1;
        if (this.definition.config[0] === "Percentage") {
            let sum = 0;
            for (let x = 0; x < xCount; x++) {
                for (let y = 0; y < yCount; y++) {
                    sum += this.data[x * step + y * 2];
                }
            }
            k = sum === 0 ? 0 : 100.0 / sum;
        }
        let output = [];
        for (let x = 0; x < xCount; x++) {
            output[x] = [];
            for (let y = 0; y < yCount; y++) {
                output[x][y] = this.data[x * step + y * 2] * k;
            }
        }
        return output;
    };

    /**
     * 获取矩阵表中x轴各区间统计值
     * @returns {Array} number数组: x轴各区间统计值数组
     */
    this.getXHistValues = function() {
        let mode = this.definition.config[0];
        let defaultValue = 0;
        try {
            defaultValue = parseFloat(this.definition.config[11]);
        } catch (e) {}
        let xr = this.getXRange();
        let yr = this.getYRange();
        if (!xr || !yr) return [];
        let xCount = xr.count;
        let yCount = yr.count;
        let step = this.dataDim1;
        if (mode === "Percentage") {
            let values = this.getValues();
            let outputPercentage = new Array(xCount).fill(0);
            for (let i = 0; i < xCount; i++) {
                let sum = 0;
                for (let j = 0; j < yCount; j++) {
                    sum += values[i][j];
                }
                outputPercentage[i] = sum;
            }
            return outputPercentage;
        }
        let output = new Array(xCount).fill(0);
        for (let i = 0; i < xCount; i++) {
            if (mode === "Sum") {
                let sum = 0;
                for (let j = 0; j < yCount; j++) {
                    sum += this.data[i * step + j * 2];
                }
                output[i] = sum;
            } else if (mode === "Max") {
                let maximum = -Infinity;
                for (let j = 0; j < yCount; j++) {
                    if (this.data[i * step + j * 2 + 1] > 0) {
                        maximum = Math.max(maximum, this.data[i * step + j * 2]);
                    }
                }
                output[i] = maximum === -Infinity ? defaultValue : maximum;
            } else if (mode === "Min") {
                let minimum = Infinity;
                for (let j = 0; j < yCount; j++) {
                    if (this.data[i * step + j * 2 + 1] > 0) {
                        minimum = Math.min(minimum, this.data[i * step + j * 2]);
                    }
                }
                output[i] = minimum === Infinity ? defaultValue : minimum;
            } else if (mode === "Aver") {
                let sum = 0;
                let weight = 0;
                for (let j = 0; j < yCount; j++) {
                    let thisWeight = this.data[i * step + j * 2 + 1];
                    sum += thisWeight * this.data[i * step + j * 2];
                    weight += thisWeight;
                }
                output[i] = weight === 0 ? defaultValue : sum / weight;
            }
        }
        return output;
    };

    /**
     * 获取矩阵表中y轴各区间统计值
     * @returns {Array} number数组: y轴各区间统计值数组
     */
    this.getYHistValues = function() {
        let mode = this.definition.config[0];
        let defaultValue = 0;
        try {
            defaultValue = parseFloat(this.definition.config[11]);
        } catch (e) {}
        let xr = this.getXRange();
        let yr = this.getYRange();
        if (!xr || !yr) return [];
        let xCount = xr.count;
        let yCount = yr.count;
        let step = this.dataDim1;
        if (mode === "Percentage") {
            let values = this.getValues();
            let outputPercentage = new Array(yCount).fill(0);
            for (let j = 0; j < yCount; j++) {
                let sum = 0;
                for (let i = 0; i < xCount; i++) {
                    sum += values[i][j];
                }
                outputPercentage[j] = sum;
            }
            return outputPercentage;
        }
        let output = new Array(yCount).fill(0);
        for (let j = 0; j < yCount; j++) {
            if (mode === "Sum") {
                let sum = 0;
                for (let i = 0; i < xCount; i++) {
                    sum += this.data[i * step + j * 2];
                }
                output[j] = sum;
            } else if (mode === "Max") {
                let maximum = -Infinity;
                for (let i = 0; i < xCount; i++) {
                    if (this.data[i * step + j * 2 + 1] > 0) {
                        maximum = Math.max(maximum, this.data[i * step + j * 2]);
                    }
                }
                output[j] = maximum === -Infinity ? defaultValue : maximum;
            } else if (mode === "Min") {
                let minimum = Infinity;
                for (let i = 0; i < xCount; i++) {
                    if (this.data[i * step + j * 2 + 1] > 0) {
                        minimum = Math.min(minimum, this.data[i * step + j * 2]);
                    }
                }
                output[j] = minimum === Infinity ? defaultValue : minimum;
            } else if (mode === "Aver") {
                let sum = 0;
                let weight = 0;
                for (let i = 0; i < xCount; i++) {
                    let thisWeight = this.data[i * step + j * 2 + 1];
                    sum += thisWeight * this.data[i * step + j * 2];
                    weight += thisWeight;
                }
                output[j] = weight === 0 ? defaultValue : sum / weight;
            }
        }
        return output;
    };
}
export { AppoxMatrixTableData };

/**
 * 标签表数据
 * @constructor
 * @param {Object} source 数据源
 */
function AppoxLabelTableData(source) {

    this.id = source.id;
    this.definition = source.definition;
    this.params = source.params;
    this.dataDim0 = source.dataDim0;
    this.dataDim1 = source.dataDim1;
    this.data = source.data;

    /**
     * 获取标签表统计模式
     * @returns {LabelTableMode} 统计模式
     */
    this.getMode = function() {
        return this.definition && this.definition.config && this.definition.config.length > 0 ? this.definition.config[0] : null;
    };

    /**
     * 获取X轴标题
     * @returns {string} X轴标题
     */
    this.getXTitle = function() {
        return this.definition && this.definition.config && this.definition.config.length > 5 ? this.definition.config[5] : "";
    };

    /**
     * 获取Y轴标题
     * @returns {string} Y轴标题
     */
    this.getYTitle = function() {
        return this.definition && this.definition.config && this.definition.config.length > 6 ? this.definition.config[6] : "";
    };

    /**
     * 获取x轴标签个数
     * @returns {number} x轴标签个数
     */
    this.getXLabelCount = function() {
        try {
            return parseInt(this.definition.config[1]);
        } catch (e) { return 0; }
    };
    
    /**
     * 获取y轴标签个数
     * @returns {number} y轴标签个数
     */
    this.getYLabelCount = function() {
        try {
            return parseInt(this.definition.config[2]);
        } catch (e) { return 0; }
    };

    /**
     * 获取x轴标签列表
     * @returns {Array} string数组: x轴标签列表
     */
    this.getXLabels = function() {
        let count = this.getXLabelCount();
        let output = [];
        for (let i = 0; i < count; i++) {
            output.push(this.definition.config[7 + i]);
        }
        return output;
    };

    /**
     * 获取y轴标签列表
     * @returns {Array} string数组: y轴标签列表
     */
    this.getYLabels = function() {
        let xCount = this.getXLabelCount();
        let yCount = this.getYLabelCount();
        let output = [];
        for (let i = 0; i < yCount; i++) {
            output.push(this.definition.config[7 + xCount + i]);
        }
        return output;
    };

    /**
     * 获取标签表数据方向
     * @returns {LabelTableValueDirection} 数据方向
     */
    this.getValueDirection = function() {
        return this.definition && this.definition.config && this.definition.config.length > 3 ? this.definition.config[3] : null;
    };

    /**
     * 获取标签表默认值
     * @returns {number} 默认值
     */
    this.getDefaultValue = function() {
        try {
            return parseFloat(this.definition.config[4]);
        } catch (e) { return 0; }
    };

    /**
     * 检查是否存在数据
     * @returns {boolean} 是否存在数据
     */
    this.hasData = function() {
        if (!this.data || this.data.length === 0) return false;
        let xr = this.getXLabelCount();
        let yr = this.getYLabelCount();
        let defaultValue = this.getDefaultValue();
        let step = this.dataDim1;
        for (let x = 0; x < xr; x++) {
            for (let y = 0; y < yr; y++) {
                if (this.data[x * step + y * 2] !== defaultValue) return true;
                if (this.data[x * step + y * 2 + 1] !== 0) return true;
            }
        }
        return false;
    };

    /**
     * 获取统计数据
     * @returns {Array} number二维数组: 统计数据
     */
    this.getValues = function() {
        let xc = this.getXLabelCount();
        let yc = this.getYLabelCount();
        let step = this.dataDim1;
        let k = 1;
        if (this.definition.config[0] === "Percentage") {
            let sum = 0;
            for (let x = 0; x < xc; x++) {
                for (let y = 0; y < yc; y++) {
                    sum += this.data[x * step + y * 2];
                }
            }
            k = sum === 0 ? 0 : 100 / sum;
        }
        let output = [];
        for (let x = 0; x < xc; x++) {
            output[x] = [];
            for (let y = 0; y < yc; y++) {
                output[x][y] = this.data[x * step + y * 2] * k;
            }
        }
        return output;
    };

    /**
     * 获取x轴各标签统计值
     * @returns {Array} number数组: x轴各标签统计值
     */
    this.getXHistValues = function() {
        let mode = this.definition.config[0];
        let defaultValue = this.getDefaultValue();
        let xCount = this.getXLabelCount();
        let yCount = this.getYLabelCount();
        let step = this.dataDim1;
        if (mode === "Percentage") {
            let values = this.getValues();
            let outputPercentage = new Array(xCount).fill(0);
            for (let i = 0; i < xCount; i++) {
                let sum = 0;
                for (let j = 0; j < yCount; j++) {
                    sum += values[i][j];
                }
                outputPercentage[i] = sum;
            }
            return outputPercentage;
        }
        let output = new Array(xCount).fill(0);
        for (let i = 0; i < xCount; i++) {
            if (mode === "Sum") {
                let sum = 0;
                for (let j = 0; j < yCount; j++) {
                    sum += this.data[i * step + j * 2];
                }
                output[i] = sum;
            } else if (mode === "Max") {
                let maximum = -Infinity;
                for (let j = 0; j < yCount; j++) {
                    if (this.data[i * step + j * 2 + 1] > 0) {
                        maximum = Math.max(maximum, this.data[i * step + j * 2]);
                    }
                }
                output[i] = maximum === -Infinity ? defaultValue : maximum;
            } else if (mode === "Min") {
                let minimum = Infinity;
                for (let j = 0; j < yCount; j++) {
                    if (this.data[i * step + j * 2 + 1] > 0) {
                        minimum = Math.min(minimum, this.data[i * step + j * 2]);
                    }
                }
                output[i] = minimum === Infinity ? defaultValue : minimum;
            } else if (mode === "Aver") {
                let sum = 0;
                let weight = 0;
                for (let j = 0; j < yCount; j++) {
                    let thisWeight = this.data[i * step + j * 2 + 1];
                    sum += thisWeight * this.data[i * step + j * 2];
                    weight += thisWeight;
                }
                output[i] = weight === 0 ? defaultValue : sum / weight;
            }
        }
        return output;
    };

    /**
     * 获取y轴各标签统计值
     * @returns {Array} number数组: y轴各标签统计值
     */
    this.getYHistValues = function() {
        let mode = this.definition.config[0];
        let defaultValue = this.getDefaultValue();
        let xCount = this.getXLabelCount();
        let yCount = this.getYLabelCount();
        let step = this.dataDim1;
        if (mode === "Percentage") {
            let values = this.getValues();
            let outputPercentage = new Array(yCount).fill(0);
            for (let j = 0; j < yCount; j++) {
                let sum = 0;
                for (let i = 0; i < xCount; i++) {
                    sum += values[i][j];
                }
                outputPercentage[j] = sum;
            }
            return outputPercentage;
        }
        let output = new Array(yCount).fill(0);
        for (let j = 0; j < yCount; j++) {
            if (mode === "Sum") {
                let sum = 0;
                for (let i = 0; i < xCount; i++) {
                    sum += this.data[i * step + j * 2];
                }
                output[j] = sum;
            } else if (mode === "Max") {
                let maximum = -Infinity;
                for (let i = 0; i < xCount; i++) {
                    if (this.data[i * step + j * 2 + 1] > 0) {
                        maximum = Math.max(maximum, this.data[i * step + j * 2]);
                    }
                }
                output[j] = maximum === -Infinity ? defaultValue : maximum;
            } else if (mode === "Min") {
                let minimum = Infinity;
                for (let i = 0; i < xCount; i++) {
                    if (this.data[i * step + j * 2 + 1] > 0) {
                        minimum = Math.min(minimum, this.data[i * step + j * 2]);
                    }
                }
                output[j] = minimum === Infinity ? defaultValue : minimum;
            } else if (mode === "Aver") {
                let sum = 0;
                let weight = 0;
                for (let i = 0; i < xCount; i++) {
                    let thisWeight = this.data[i * step + j * 2 + 1];
                    sum += thisWeight * this.data[i * step + j * 2];
                    weight += thisWeight;
                }
                output[j] = weight === 0 ? defaultValue : sum / weight;
            }
        }
        return output;
    };
}
export { AppoxLabelTableData };

/**
 * 框架相关的枚举类型集
 */
var appoxEnums = new AppoxEnums();
function AppoxEnums() {

    /**
     * 清单信息等级 
     * @enum
     */
    this.LogLevel = {
        /** 信息，绿色显示 */
        Info : "Info",
        /** 警告，橙色显示 */
        Warning : "Warning",
        /** 错误，红色显示 */
        Error : "Error",
    };
    
    /**
     * 语言
     * @enum
     */
    this.Language = {
        /** 无效值 */
        Invalid : "Invalid",
        /** 英文，语言代号为"en" */
        English : "English",
        /** 中文，语言代号为"zh"，默认为简体中文 */
        Chinese : "Chinese",
    };
    
    /**
     * 独立任务状态
     * @enum
     */
    this.TaskState = {
        /** 任务还未开始 */
        Unknown : "Unknown",
        /** 任务完成 */
        Finished : "Finished",
        /** 任务失败或被中止 */
        Failed : "Failed",
        /** 任务进行中 */
        Progressing : "Progressing",
    };

    /**
     * 设备状态
     * @enum
     */
    this.GeneralDeviceStatus = {
        /** 未启用连接 */
        None : "None",
        /** 连接成功 */
        OK : "OK",
        /** 未连接或连接状态不正常 */
        Error : "Error",
        /** 部分设备未连接或连接状态不正常 */
        Warning : "Warning",
    };

    /**
     * 应用基于的图形界面框架
     * @enum
     */
    this.ApplicationGUI = {
        /** 无图形界面 */
        NoGUI : "NoGUI",
        /** 未知（获取失败等情况） */
        Unknown : "Unknown",
    };

    /**
     * 应用当前的运行模式
     * @enum
     */
    this.ApplicationMode = {
        /** 在线采集模式 */
        Online : "Online",
        /** 离线处理模式 */
        Offline : "Offline",
        /** 离线回放模式 */
        Replay : "Replay",
        /** 远程采集模式 */
        Remote : "Remote",
        /** 未知（获取失败等情况） */
        Unknown : "Unknown",
    };

    /**
     * 应用当前的运行状态
     * @enum
     */
    this.ApplicationStatus = {
        /** 空闲 */
        Idle : "Idle",
        /** 运行session中 */
        Running : "Running",
        /** 启动session中 */
        Starting : "Starting",
        /** 停止session中 */
        Stopping : "Stopping",
        /** 运行独立任务中 */
        Tasking : "Tasking",
        /** 未知（获取失败等情况） */
        Unknown : "Unknown",
    };

    /**
     * 总线设备通道类型
     * @enum
     */
    this.BusChannelType = {
        /** 无效类型 */
        None : "None",
        /** CAN总线，报文数据即payload，1～8字节 */
        Can : "Can",
        /** CAN-FD总线，报文数据即payload，1～64字节 */
        CanFD : "CanFD",
        /** LIN总线，报文数据即payload，1～8字节 */
        Lin : "Lin",
        /** Flexray总线，报文数据由标志位字节(从低至高为startup,sync,null)、cycle字节和payload构成(共2～256字节)，报文ID即Slot ID */
        Flexray : "Flexray",
        /** 以太网总线，报文数据为包含链路层等等协议的完整以太网帧数据，报文ID定义为源MAC的后四字节(小字序) */
        Ethernet : "Ethernet",
        /** SOME/IP车载以太网总线，报文数据为包含链路层等等协议的完整以太网帧数据，报文ID即Message ID(由Service ID和Method ID组成) */
        SomeIP : "SomeIP",
    };

    /**
     * 总线协议文件状态
     * @enum
     */
    this.BusProtocolFileState = {
        /** 正常 */
        OK : "OK",
        /** 未在协议库中找到 */
        NotFoundInLibrary : "NotFoundInLibrary",
        /** 协议文件不存在 */
        FileNotExist : "FileNotExist",
        /** 协议文件内容不匹配 */
        MD5NotCorrect : "MD5NotCorrect",
        /** 未知（获取失败等情况） */
        Unknown : "Unknown",
    };

    /**
     * 时间偏置同步状态
     * @enum
     */
    this.TimeOffsetSync = {
        /** 到达主机的时间，未同步 */
        HostArrival : "HostArrival",
        /** 采样时间，已与授时服务器同步 */
        Server : "Server",
        /** 采样时间，已与卫星同步 */
        Gnss : "Gnss",
        /** 到达总线设备的时间，已与授时服务器同步 */
        BusReceiverArrival : "BusReceiverArrival",
        /** 同步状态不同的两个样本插值得到的时间 */
        Interpolated : "Interpolated",
    };

    /**
     * 组件配置的状态
     * @enum
     */
    this.ConfigStatus = {
        /** 启用且正常 */
        Enabled : "Enabled",
        /** 启用但存在错误 */
        EnabledWithError : "EnabledWithError",
        /** 启用但部分存在错误 */
        EnabledWithWarning : "EnabledWithWarning",
        /** 禁用 */
        Disabled : "Disabled",
    };

    /**
     * 事件session记录状态
     * @enum
     */
    this.EventSessionRecordStatus = {
        /** 未启用事件session记录 */
        Disabled : "Disabled",
        /** 记录中 */
        Recording : "Recording",
        /** 已记录至本地存储 */
        Recorded : "Recorded",
    };

    /**
     * Generation的状态信息
     * @enum
     */
    this.GenerationProcessStatus = {
        /** 未知（在较老版本中输出的Generation不包括状态信息） */
        Unknown : "Unknown",
        /** 处理完毕 */
        Finished : "Finished",
        /** 未处理完毕（在处理过程中手动停止或卡死自动跳过导致） */
        NotFinished : "NotFinished",
    };

    /**
     * 图表类型
     * @enum
     */
    this.GraphType = {
        /** 无效图表 */
        Invalid : "Invalid",
        /** 单值 */
        SingleValue : "SingleValue",
        /** 散点图 */
        ScatterPoints : "ScatterPoints",
        /** 直方折线图 */
        HistAndLine : "HistAndLine",
        /** 矩阵表 */
        MatrixTable : "MatrixTable",
        /** 标签表 */
        LabelTable : "LabelTable",
    };

    /**
     * 独立显卡厂商
     * @enum
     */
    this.GraphicCardVendor = {
        /** 未知 */
        Unknown : "Unknown",
        /** 英伟达 */
        NVidia : "NVidia",
        /** AMD */
        AMD : "AMD",
    }

    /**
     * 使用视频通道
     * @enum
     */
    this.UsingVideoChannel = {
        /** 通道A */
        ChannelA : "ChannelA",
        /** 通道B */
        ChannelB : "ChannelB",
        /** 通道C */
        ChannelC : "ChannelC",
        /** 通道D */
        ChannelD : "ChannelD",
        /** 通道E */
        ChannelE : "ChannelE",
        /** 通道F */
        ChannelF : "ChannelF",
        /** 通道G */
        ChannelG : "ChannelG",
        /** 通道H */
        ChannelH : "ChannelH",
        /** 通道I */
        ChannelI : "ChannelI",
        /** 通道J */
        ChannelJ : "ChannelJ",
        /** 通道K */
        ChannelK : "ChannelK",
        /** 通道L */
        ChannelL : "ChannelL",
        /** 通道M */
        ChannelM : "ChannelM",
        /** 通道N */
        ChannelN : "ChannelN",
        /** 通道O */
        ChannelO : "ChannelO",
        /** 通道P */
        ChannelP : "ChannelP",
        /** 通道Q */
        ChannelQ : "ChannelQ",
        /** 通道R */
        ChannelR : "ChannelR",
        /** 通道S */
        ChannelS : "ChannelS",
        /** 通道T */
        ChannelT : "ChannelT",
        /** 通道U */
        ChannelU : "ChannelU",
        /** 通道V */
        ChannelV : "ChannelV",
        /** 通道W */
        ChannelW : "ChannelW",
        /** 通道X */
        ChannelX : "ChannelX",
        /** 特殊摄像头：前向（标准模型） */
        SpecialFront : "SpecialFront",
        /** 特殊摄像头：左侧车道线 */
        SpecialLeftLine : "SpecialLeftLine",
        /** 特殊摄像头：右侧车道线 */
        SpecialRightLine : "SpecialRightLine",
        /** 特殊摄像头：左侧盲区 */
        SpecialLeftBS : "SpecialLeftBS",
        /** 特殊摄像头：右侧盲区 */
        SpecialRightBS : "SpecialRightBS",
        /** 特殊摄像头：前向（鱼眼模型） */
        SpecialFisheyeFront : "SpecialFisheyeFront",
    };

    /**
     * 文件写入需要的数据类型
     * @enum
     */
    this.RecordDataType = {
        /** 无效值 */
        Invalid : "Invalid",
        /** 总线原始数据 */
        BusRawData : "BusRawData",
        /** 总线协议报文数据 */
        BusMessages : "BusMessages",
        /** 视频原始数据 */
        VideoRawData : "VideoRawData",
        /** 用于数据处理的视频数据 */
        VideoProcData : "VideoProcData",
        /** 信号数据 */
        Signals : "Signals",
        /** 样本数据 */
        Samples : "Samples",
        /** 矩阵数据 */
        Matrices : "Matrices",
    };

    /**
     * 原生插件类别
     * @enum
     */
    this.NativeLibraryType = {
        /** 一般原生库 */
        Native : "Native",
        /** 总线设备库 */
        Bus : "Bus",
        /** 视频设备库 */
        Video : "Video",
        /** 数据处理库 */
        Processor : "Processor",
        /** 一般设备库 */
        Device : "Device",
        /** 文件读写库 */
        FileIO : "FileIO",
    };

    /**
     * 系统状态
     * @enum
     */
    this.SystemStatus = {
        /** 实际回放速度（倍速） */
        ActualReplaySpeed : "ActualReplaySpeed",
        /** 目标回放速度（倍速） */
        TargetReplaySpeed : "TargetReplaySpeed",
        /** 最新清单消息 */
        CurrentLoggerMessage : "CurrentLoggerMessage",
        /** 显示延迟，单位毫秒 */
        DisplayLag : "DisplayLag",
        /** 连续数据或缓存数据写入队列长度，单位秒 */
        ContinuousFileWriteQueue : "ContinuousFileWriteQueue",
        /** 事件数据写入队列长度，单位秒 */
        EventFileWriteQueue : "EventFileWriteQueue",
        /** 视频处理队列长度限制 */
        VideoProcessQueueCapacity : "VideoProcessQueueCapacity",
        /** 视频处理队列长度 */
        VideoProcessQueue : "VideoProcessQueue",
        /** 音量（倍数） */
        AudioVolume : "AudioVolume",
        /** CPU使用率，单位百分比 */
        CPUUsage : "CPUUsage",
        /** CPU使用率的乘数，固定为1 */
        CPUUsageRatio : "CPUUsageRatio",
        /** 内存总容量，单位字节 */
        MemoryCapacity : "MemoryCapacity",
        /** 内存可用容量，单位字节 */
        MemoryFree : "MemoryFree",
        /** 内存可用容量的警告阈值，单位字节 */
        MemoryWarningThreshold : "MemoryWarningThreshold",
        /** 内存可用容量的最小阈值，单位字节 */
        MemoryErrorThreshold : "MemoryErrorThreshold",
        /** 当前数据目录所在磁盘的总容量，单位字节 */
        StorageCapacity : "StorageCapacity",
        /** 当前数据目录所在磁盘可用容量，单位字节 */
        StorageFree : "StorageFree",
        /** 根据当前数据目录所在磁盘可用容量预估的时长，单位小时 */
        StorageFreeHours : "StorageFreeHours",
        /** 当前数据目录所在磁盘可用容量的警告阈值，单位字节 */
        StorageWarningThreshold : "StorageWarningThreshold",
        /** 当前数据目录所在磁盘可用容量的最小阈值，单位字节 */
        StorageErrorThreshold : "StorageErrorThreshold",
        /** 最近一次基础线程心跳时间，格式为yyyyMMddHHmmss.fff */
        WorkthreadHeartBeatTime : "WorkthreadHeartBeatTime",
        /** 基础线程当前运行位置 */
        WorkthreadCurrentLocation : "WorkthreadCurrentLocation",
        /** 基础线程循环平均运行时间（最近），单位毫秒 */
        WorkthreadLoopTime : "WorkthreadLoopTime",
        /** 最近一次处理线程心跳时间，格式为yyyyMMddHHmmss.fff */
        ProcthreadHeartBeatTime : "ProcthreadHeartBeatTime",
        /** 处理线程当前运行位置 */
        ProcthreadCurrentLocation : "ProcthreadCurrentLocation",
        /** 处理线程循环平均运行时间（最近），单位毫秒 */
        ProcthreadLoopTime : "ProcthreadLoopTime",
        /** 主线程循环平均运行时间（最近），单位毫秒 */
        MainthreadLoopTime : "MainthreadLoopTime",
        /** 总线数据流量，单位字节 */
        BusDataFlow : "BusDataFlow",
        /** 总线设备接收一帧数据的最大耗时，单位微秒 */
        BusDeviceReadTime : "BusDeviceReadTime",
        /** 视频数据流量，单位像素数 */
        VideoDataFlow : "VideoDataFlow",
        /** 视频设备接收一帧数据的最大耗时，单位微秒 */
        VideoDeviceReadTime : "VideoDeviceReadTime",
        /** 开始session耗时，单位毫秒 */
        StartSessionTime : "StartSessionTime",
        /** 结束session耗时，单位毫秒 */
        StopSessionTime : "StopSessionTime",
        /** 回放速度瓶颈 */
        ReplayNeck : "ReplayNeck",
        /** 文件读取线程循环平均运行时间（最近），单位毫秒 */
        FileReadThreadLoopTime : "FileReadThreadLoopTime",
        /** 文件写入线程循环平均运行时间（最近），单位毫秒 */
        FileWriteThreadLoopTime : "FileWriteThreadLoopTime",
    };

    /**
     * 插件包状态
     * @enum
     */
    this.PluginPackStatus = {
        /** 已禁用 */
        Disabled : "Disabled",
        /** 已启用 */
        Enabled : "Enabled",
        /** 应用程序重启后启用 */
        ToBeEnabled : "ToBeEnabled",
    };

    /**
     * 插件包错误信息
     * @enum
     */
    this.PluginPackError = {
        /** 正常 */
        OK : "OK",
        /** 禁用中 */
        Disabled : "Disabled",
        /** 加载失败 */
        LoadFailed : "LoadFailed",
        /** 未许可 */
        Unlicensed : "Unlicensed",
        /** 当前图形界面框架不支持，但非界面功能仍可使用 */
        GUIUnsupported : "GUIUnsupported",
    };

    /**
     * 特殊摄像头类型
     * @enum
     */
    this.SpecialCameraType = {
        /** 无位置摄像头（标准模型），使用 DefaultCameraMeta */
        Normal : "Normal",
        /** 前向摄像头（标准模型），使用 GenericCameraMeta */
        FrontCamera : "FrontCamera",
        /** 垂直面向左车道线摄像头（标准模型），使用 LaneLineCameraMeta */
        LeftLaneCamera : "LeftLaneCamera",
        /** 垂直面向右车道线摄像头（标准模型），使用 LaneLineCameraMeta */
        RightLaneCamera : "RightLaneCamera",
        /** 面向左盲区摄像头（标准模型），使用 BlindSpotCameraMeta */
        LeftBlindSpotCamera : "LeftBlindSpotCamera",
        /** 面向右盲区摄像头（标准模型），使用 BlindSpotCameraMeta */
        RightBlindSpotCamera : "RightBlindSpotCamera",
        /** 任意位置摄像头（标准模型），使用 GenericCameraMeta */
        AnyCamera : "AnyCamera",
        /** 无位置摄像头（鱼眼模型），使用 FisheyeCameraMeta */
        NormalFisheyeCamera : "NormalFisheyeCamera",
        /** 前向摄像头（鱼眼模型），使用 FisheyeCameraMeta */
        FrontFisheyeCamera : "FrontFisheyeCamera",
        /** 任意位置摄像头（鱼眼模型），使用 FisheyeCameraMeta */
        AnyFisheyeCamera : "AnyFisheyeCamera",
        /** 自定义模型的任意位置摄像头，使用 CustomizedCameraMeta */
        CustomizedCamera : "CustomizedCamera",
        /** 未知（获取失败等情况） */
        Unknown : "Unknown",
    };

    /**
     * 信号树节点类别
     * @enum
     */
    this.SignalTreeNodeType = {
        /** I级：信号大类 */
        Category : "Category",
        /** I级：总线协议 */
        BusProtocol : "BusProtocol",
        /** II级：信号小类 */
        Type : "Type",
        /** II级：总线报文 */
        BusMessage : "BusMessage",
        /** III级：一般信号 */
        GeneralSignal : "GeneralSignal",
        /** III级：系统信号 */
        SystemSignal : "SystemSignal",
        /** III级：一般总线信号 */
        NormalBusSignal : "NormalBusSignal",
        /** III级：复用的总线信号 */
        MultiplexedBusSignal : "MultiplexedBusSignal",
    };

    /**
     * 视频编码格式
     * @enum
     */
    this.VideoDataCodec = {
        /** 无效 */
        Invalid : "Invalid",
        /** MJPEG：有损编码，帧间独立 */
        MJPEG : "MJPEG",
        /** H.264：有损编码，帧间依赖 */
        H264 : "H264",
        /** YUV411：无损编码，帧间独立，格式为每8像素(U0 Y0 V0 Y1 U4 Y2 V4 Y3 Y4 Y5 Y6 Y7)，每数值8bit */
        YUV411 : "YUV411",
        /** YUV420：无损编码，帧间独立，格式为每2x2像素(U V Y00 Y01 Y10 Y11)，每数值8bit */
        YUV420 : "YUV420",
        /** H.265：有损编码，帧间依赖 */
        H265 : "H265",
        /** YUV422：无损编码，帧间独立，格式为每2像素(Y0 U Y1 V)，每数值8bit */
        YUV422 : "YUV422",
        /** RAW：无损编码，帧间独立，格式为024...行BG，135...行GR，每数值8bit */
        RAW : "RAW",
        /** RAW12：无损编码，帧间独立，格式为024...行BG，135...行GR，每数值12bit按小字序依次存储 */
        RAW12 : "RAW12",
        /** RAW14：无损编码，帧间独立，格式为024...行BG，135...行GR，每数值14bit按小字序依次存储 */
        RAW14 : "RAW14",
        /** RAW16：无损编码，帧间独立，格式为024...行BG，135...行GR，每数值16bit按大字序依次存储 */
        RAW16 : "RAW16",
        /** Y16：无损编码，帧间独立，每数值16bit按大字序依次存储 */
        Y16 : "Y16",
    };

    /**
     * 获取视频帧的模式
     * @enum
     */
    this.VideoFrameGetMode = {
        /** 固定输出VGA尺寸图像数据 */
        Preview : "Preview",
        /** 输出原始尺寸图像数据(若条件不满足则退化为Preview) */
        RawFull : "RawFull",
        /** 输出按原始尺寸1/2缩小后的图像数据(若条件不满足则退化为Preview) */
        RawHalf : "RawHalf",
        /** 输出按原始尺寸1/4缩小后的图像数据(若条件不满足则退化为Preview) */
        RawQuarter : "RawQuarter",
    };

    /**
     * 总线数据的(发送)状态
     * @enum
     */
    this.BusRawDataState = {
        /** 收到的报文，其他状态都为发送报文 */
        Received : "Received",
        /** 未运行 */
        NotRunning : "NotRunning",
        /** 无效通道 */
        InvalidChannel : "InvalidChannel",
        /** 插件未找到 */
        PluginNotFound : "PluginNotFound",
        /** 未同步 */
        NotSync : "NotSync",
        /** 不支持预约 */
        ScheduleUnsupported : "ScheduleUnsupported",
        /** 时间乱序 */
        TimeDisorder : "TimeDisorder",
        /** 发送成功 */
        TransmitOK : "TransmitOK",
        /** 发送失败 */
        TransmitFailed : "TransmitFailed",
        /** 预约成功 */
        ScheduleOK : "ScheduleOK",
        /** 预约失败 */
        ScheduleFailed : "ScheduleFailed",
    };

    /**
     * 任务运行结果
     * @enum
     */
    this.TaskResult = {
        /** 运行成功 */
        RunOK : "RunOK",
        /** 运行失败 */
        RunFailed : "RunFailed",
        /** 无法找到任务类别 */
        TaskClassNotFound : "TaskClassNotFound",
        /** 任务初始化失败 */
        TaskInitFailed : "TaskInitFailed",
        /** 非空闲状态无法运行 */
        NotIdle : "NotIdle",
        /** 未知（获取失败等情况） */
        Unknown : "Unknown",
    };

    /**
     * 往客户端传输的数据流类型
     * @enum
     */
    this.TransferStreamType = {
        /** 总线报文数据 */
        BusMessage : "BusMessage",
        /** 视频帧数据 */
        VideoFrame : "VideoFrame",
        /** 音频帧数据 */
        AudioFrame : "AudioFrame",
        /** 信号数据 */
        Signal : "Signal",
        /** 通用样本数据 */
        GeneralSample : "GeneralSample",
        /** 点云数据 */
        PointCloud : "PointCloud",
        /** 功能数据 */
        Function : "Function",
    }

    /**
     * 直方折线图统计模式
     * @enum
     */
    this.HistLineMode = {
        /** 求和直方图 */
        Sum: "Sum",
        /** 平均值直方图 */
        Aver: "Aver",
        /** 最小值直方图 */
        Min: "Min",
        /** 最大值直方图 */
        Max: "Max",
        /** 百分比直方图 */
        Percentage: "Percentage",
        /** 求和直方图，求和折线图 */
        SumAndSum: "SumAndSum",
        /** 最小值直方图，最小值折线图 */
        MinAndMin: "MinAndMin",
        /** 最小值直方图，最大值折线图 */
        MinAndMax: "MinAndMax",
        /** 最大值直方图，最大值折线图 */
        MaxAndMax: "MaxAndMax",
        /** 平均值直方图，最小值折线图 */
        AverAndMin: "AverAndMin",
        /** 平均值直方图，最大值折线图 */
        AverAndMax: "AverAndMax",
        /** 平均值直方图，平均值折线图 */
        AverAndAver: "AverAndAver",
        /** 平均值直方图，标准差折线图 */
        AverAndDev: "AverAndDev",
        /** 命中率直方图 */
        HitRatio: "HitRatio"
    }

    /**
     * 矩阵表统计模式
     * @enum
     */
    this.MatrixTableMode = {
        /** 求和矩阵表 */
        Sum: "Sum",
        /** 平均值矩阵表 */
        Aver: "Aver",
        /** 最小值矩阵表 */
        Min: "Min",
        /** 最大值矩阵表 */
        Max: "Max",
        /** 百分比矩阵表 */
        Percentage: "Percentage"
    }
    
    /**
     * 标签表统计模式
     * @enum
     */
    this.LabelTableMode = {
        /** 求和标签表 */
        Sum: "Sum",
        /** 平均值标签表 */
        Aver: "Aver",
        /** 最小值标签表 */
        Min: "Min",
        /** 最大值标签表 */
        Max: "Max",
        /** 百分比标签表 */
        Percentage: "Percentage"
    }

    /**
     * 标签表数值方向
     * @enum
     */
    this.LabelTableValueDirection = {
        /** 正向，数值可视化范围为默认值至最大值 */
        Positive: "Positive",
        /** 负向，数值可视化范围为默认值至最小值 */
        Negative: "Negative",
        /** 双向，数值可视化范围为默认值为中心的双向对称区间 */
        Bidirectional: "Bidirectional"
    }

    /**
     * 连接模式
     * @enum
     */
    this.ConnectionMode = {
        /** 未初始化 */
        NotInitialized : "NotInitialized",
        /** 后端Worker连接模式 */
        Worker : "Worker",
        /** 桥接模式 */
        Bridge: "Bridge",
    }

    /**
     * 事件快照内容类型
     * @enum
     */
    this.EventSnapshotContentType = {
        /** 信号表 */
        TableData : "TableData",
        /** 城市尺度地图图像 */
        LocationCityImage : "LocationCityImage",
        /** 街道尺度地图图像 */
        LocationRoadImage : "LocationRoadImage",
        /** 视频图像 */
        VideoImage : "VideoImage",
        /** 俯瞰图图像 */
        BirdViewImage : "BirdViewImage",
    }
}
export { appoxEnums };

/**
 * 实用功能
 */
var appoxUtility = new AppoxUtility();
function AppoxUtility() {

    /**
     * 本地获取数据缓存范围
     * @returns {AppoxBufferRange} 数据缓存范围
     */
    this.getBufferRangeLocal = () => {
        appoxi7.ensureTimelineManager();
        return appoxi7.timelineManager.getBufferRange();
    }

    /**
     * 本地获取当前兴趣点在时间线上的位置
     * @returns {number} 在时间线上的兴趣点，单位秒
     */
    this.getInterestTimeLocal = () => {
        appoxi7.ensureTimelineManager();
        return appoxi7.timelineManager.getInterest();
    }

    /**
     * 本地获取距离指定时间最近的视频帧的预览JPEG图像数据
     * @param {number} channel 视频通道，0~23
     * @param {number} timeline 获取视频帧的目标时间线，单位秒
     * @param {number} maxGap 容许的最大间隔，单位秒
     * @returns {Array} [视频帧的预览JPEG数据, 图像的时间戳, 摄像头信息]，获取失败则各项为null
     */
    this.getPreviewJpegLocal = (channel, timeline, maxGap) => {
        appoxi7.ensureVideoBuffer();
        return appoxi7.videoBuffer.getPreviewJpeg(channel, timeline, maxGap);
    }

    /**
     * 解析通用样本为特化样本
     * @param {AppoxGeneralSample} generalSample 通用样本
     * @param {Object} converters 转换器
     * @returns {Object} 解析后的样本
     */
    this.parseSample = (generalSample, converters) => {
        if (generalSample == null) return null;
        if (!(generalSample instanceof AppoxGeneralSample)) return null;
        let func = converters[generalSample.protocol];
        if (func == null) return null;
        return func(generalSample);
    }

    /**
     * 按目标时间点搜索缓存
     * @param {Array} buffer 数组: 样本缓存
     * @param {number} targetTimeline 在时间线上的目标时间点，单位秒
     * @param {number} maxGap 最大时间间隔，若最近样本的时间间隔大于此值，则不考虑
     * @returns {AppoxSamplePair} 搜索结果
     */
    this.searchSample = (buffer, targetTimeline, maxGap = 1.0) => {
        if (buffer == null || buffer.length == 0) return null;

        if (buffer.length == 1) {
            if (Math.abs(targetTimeline - buffer[0].timeline) > maxGap) return null;
            return new AppoxSamplePair(buffer[0], 1, 0, buffer[0], 0, 0);
        }

        if (targetTimeline > buffer[buffer.length - 1].timeline + maxGap) return null;
        if (targetTimeline < buffer[0].timeline - maxGap) return null;

        if (targetTimeline > buffer[buffer.length - 1].timeline) {
            let lastSample = buffer[buffer.length - 1];
            return new AppoxSamplePair(lastSample, 1, buffer.length - 1, lastSample, 0, buffer.length - 1);
        }
        if (targetTimeline < buffer[0].timeline) {
            let firstSample = buffer[0];
            return new AppoxSamplePair(firstSample, 1, 0, firstSample, 0, 0);
        }

        let searchSampleRecursive = (middle, left, right, targetTime) => {
            if (left == middle) return [middle, right];
            if (right == middle) return [left, middle];
            if (buffer[middle].timeline > targetTime) {
                return searchSampleRecursive(Math.floor((left + middle) / 2), left, middle, targetTime);
            } else {
                return searchSampleRecursive(Math.floor((right + middle) / 2), middle, right, targetTime);
            }
        };
    
        let range = searchSampleRecursive(Math.floor(buffer.length / 2), 0, buffer.length - 1, targetTimeline);

        if (range[0] == range[1]) {
            let sample = buffer[range[0]];
            if (Math.abs(sample.timeline - targetTimeline) > maxGap) return null;
            return new AppoxSamplePair(sample, 1, range[0], sample, 0, range[0]);
        }

        let s1 = buffer[range[0]];
        let s2 = buffer[range[1]];
        let t1 = s1.timeline;
        let t2 = s2.timeline;
        let t1Gap = Math.abs(t1 - targetTimeline);
        let t2Gap = Math.abs(t2 - targetTimeline);

        if (t2 <= t1 && t1Gap < maxGap) {
            return new AppoxSamplePair(s1, 1, range[0], s1, 0, range[0]);
        }
        else if (t1Gap < maxGap && t2Gap < maxGap) {
            if (s1.timestamp.session.equals(s2.timestamp.session)) {
                let timestampDelta = t2 - t1;
                let w1 = t2Gap / timestampDelta;
                let w2 = t1Gap / timestampDelta;
                return new AppoxSamplePair(s1, w1, range[0], s2, w2, range[1]);
            } else {
                if (t1Gap < t2Gap) {
                    return new AppoxSamplePair(s1, 1, range[0], s1, 0, range[0]);
                } else {
                    return new AppoxSamplePair(s2, 1, range[1], s2, 0, range[1]);
                }
            }
        }
        else if (t1Gap < maxGap && t2Gap >= maxGap) {
            return new AppoxSamplePair(s1, 1, range[0], s1, 0, range[0]);
        }
        else if (t1Gap >= maxGap && t2Gap < maxGap) {
            return new AppoxSamplePair(s2, 1, range[1], s2, 0, range[1]);
        }
        
        return null;
    };

    /**
     * 在搜索结果中获取最近样本并转化为特化样本
     * @param {AppoxSamplePair} pair 搜索结果
     * @param {Object} converters 转换器
     * @returns {Object} 特化样本
     */
    this.getNearestSample = (pair, converters) => {
        if (pair == null || converters == null) return null;
        return this.parseSample(pair.weight1 > pair.weight2 ? pair.sample1 : pair.sample2, converters);
    }

    /**
     * 按目标时间点搜索缓存并获取最近样本
     * @param {Array} buffer 数组: 样本缓存
     * @param {number} targetTimeline 在时间线上的目标时间点，单位秒
     * @param {Object} converters 转换器
     * @param {number} maxGap 最大时间间隔，若最近样本的时间间隔大于此值，则不考虑
     * @returns {Object} 特化样本
     */
    this.searchAndGetNearestSample = (buffer, targetTimeline, converters, maxGap = 1.0) => {
        let pair = this.searchSample(buffer, targetTimeline, maxGap);
        if (pair == null) return null;
        return this.getNearestSample(pair, converters);
    }

    /**
     * 在搜索结果中插值并转化为特化样本
     * @param {AppoxSamplePair} pair 搜索结果
     * @param {Object} converters 转换器
     * @param {Object} interpolator 插值器
     * @returns {Object} 特化样本
     */
    this.interpolateSample = (pair, converters, interpolator) => {
        if (pair == null || converters == null || interpolator == null) return null;
        let convertedSample1 = this.parseSample(pair.sample1, converters);
        let convertedSample2 = this.parseSample(pair.sample2, converters);
        if (convertedSample1 == null || convertedSample2 == null) return null;
        let session = pair.sample1.timestamp.session;
        let time = pair.sample1.time * pair.weight1 + pair.sample2.time * pair.weight2;
        return interpolator(session, time, convertedSample1, pair.weight1, convertedSample2, pair.weight2);
    }

    /**
     * 按目标时间点搜索缓存并插值并转化为特化样本
     * @param {Array} buffer 数组: 样本缓存
     * @param {number} targetTimeline 在时间线上的目标时间点，单位秒
     * @param {Object} converters 转换器
     * @param {Object} interpolator 插值器
     * @param {number} maxGap 最大时间间隔，若最近样本的时间间隔大于此值，则不考虑
     * @returns {Object} 特化样本
     */
    this.searchAndInterpolateSample = (buffer, targetTimeline, converters, interpolator, maxGap = 1.0) => {
        let pair = this.searchSample(buffer, targetTimeline, maxGap);
        if (pair == null) return null;
        return this.interpolateSample(pair, converters, interpolator);
    }

    /**
     * 根据全局缓存范围，将样本缓存中较早样本移除
     * @param {Array} buffer 数组: 样本缓存
     */
    this.clipSampleBuffer = (buffer) => {
        if (buffer == null || buffer.length == 0) return;
        let removeCount = 0;
        let begin = this.getBufferRangeLocal().begin;
        for (let sample of buffer) {
            if (sample.timeline < begin) removeCount++;
            else break;
        }
        if (removeCount > 0) {
            buffer.splice(0, removeCount);
        }
    }

    /**
     * 获取指定session在时间线上的开始时间点
     * @param {AppoxSessionIdentifier} session Session ID
     * @returns {number} 在时间线上的开始时间点，获取失败则返回null
     */
    this.getSessionTimelineLocal = (session) => {
        appoxi7.ensureSessionSamplesContext(session);
        return appoxi7.sessionSamplesCtxs[session].sessionTimeline;
    }

    /**
     * 启动控制台过程，配置数据加密
     */
    this.configDataEncryption = async () => {
        if (appoxi7.connectionMode == appoxEnums.ConnectionMode.Worker) {
            await agencyAsync.runConsole("config-data-encryption.workerworkflow");
        }
        else if (appoxi7.connectionMode == appoxEnums.ConnectionMode.Bridge) {
            await appoxUtilityBridge.configDataEncryption();
        }
    }
}
export { appoxUtility };

/**
 * 桥接模式实用功能
 */
var appoxUtilityBridge = new AppoxUtilityBridge();
function AppoxUtilityBridge() {

    /**
     * 获取内容传输状态
     * @returns {Promise} AppoxContentTransferState: 内容传输状态
     */
    this.getContentTransferState = async () => {

        if (appoxi7.connectionMode != appoxEnums.ConnectionMode.Bridge) return new AppoxContentTransferState([], []);
        
        let parseTasks = (jsonString) => {
            if (jsonString == null || jsonString.length == 0) return [];
            let tasks = JSON.parse(jsonString);
            let output = [];
            for (let taskObj of tasks) {
                let year = taskObj.T.substring(0, 4)
                let month = taskObj.T.substring(4, 6)
                let day = taskObj.T.substring(6, 8)
                let hour = taskObj.T.substring(8, 10)
                let minute = taskObj.T.substring(10, 12)
                let second = taskObj.T.substring(12, 14)
                let millisecond = taskObj.T.substring(14, 17) || '000'
                let commitTime = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hour), parseInt(minute), parseInt(second), parseInt(millisecond))
                let guid = taskObj.G;
                let contentID = taskObj.I;
                let brief = taskObj.B;
                let payloadByteCount = taskObj.C;
                let progressDetails = taskObj.P == null ? null : new AppoxContentTransferProgressDetails(taskObj.P.S, taskObj.P.T, taskObj.P.R);
                let task = new AppoxContentTransferTask(commitTime, guid, contentID, brief, payloadByteCount, progressDetails);
                output.push(task);
            }
            return output;
        }

        let downloadTasks = parseTasks(await agencyAsync.getGlobalVariable("Console.ContentDownloadTasks", null));
        let uploadTasks = parseTasks(await agencyAsync.getGlobalVariable("Console.ContentUploadTasks", null));
        return new AppoxContentTransferState(downloadTasks, uploadTasks);
    }

    /**
     * 取消上传任务
     * @param {string} guid 任务唯一标识
     */
    this.cancelUploadTask = (guid) => {
        if (appoxi7.connectionMode != appoxEnums.ConnectionMode.Bridge) return;
        if (guid == null) return;
        agencyAsync.publishData("Console.CancelContentUploadTask", appoxi7.textEncoder.encode(guid));
    }

    /**
     * 取消下载任务
     * @param {string} guid 任务唯一标识
     */
    this.cancelDownloadTask = (guid) => {
        if (appoxi7.connectionMode != appoxEnums.ConnectionMode.Bridge) return;
        if (guid == null) return;
        agencyAsync.publishData("Console.CancelContentDownloadTask", appoxi7.textEncoder.encode(guid));
    }

    /**
     * 获取空闲端口
     * @returns {Promise} string数组: 空闲端口列表
     */
    this.getIdlePorts = async () => {
        if (appoxi7.connectionMode != appoxEnums.ConnectionMode.Bridge) return [];
        let idlePortRows = await agencyAsync.getGlobalVariable("Console.IdlePorts", null);
        if (idlePortRows == null || idlePortRows.length == 0) return [];
        return idlePortRows.split('\n');
    }

    /**
     * 移除空闲端口
     * @param {string} port 端口
     */
    this.removeIdlePort = (port) => {
        if (appoxi7.connectionMode != appoxEnums.ConnectionMode.Bridge) return;
        if (port == null) return;
        agencyAsync.publishData("Console.RemoveIdlePort", appoxi7.textEncoder.encode(port));
    }

    /**
     * 启动控制台过程，配置数据加密
     */
    this.configDataEncryption = async () => {
        if (appoxi7.connectionMode == appoxEnums.ConnectionMode.Bridge) {
            await agencyAsync.runConsole("config-data-encryption.consoleworkflow");
        }
    }
}
export { appoxUtilityBridge };

/**
 * 框架内部实现
 */
var appoxi7 = new AppoxInternal();
function AppoxInternal() {

    this.connectionMode = appoxEnums.ConnectionMode.NotInitialized;
    this.ip = "127.0.0.1";
    this.workerPort = "12344";
    this.bridgePort = "22344";
    this.bridgeConsolePort = "33345";
    this.socket = null;
    this.connected = false;
    this.lastReceiveTime = null;
    this.recreateTime = null;
    this.segmentRestore = null;
    this.commVersionMismatch = false;
    this.masks = null;
    this.randomMaskReceived = false;
    this.agencyCallCtx = new Map();
    this.agencyCounterRunLastInvoke = new Map();
    this.agencyInputRequiredPacketsSrc = [];
    this.agencyInputOptionalPacketsSrc = [];
    this.agencyInputRequiredPackets = [];
    this.agencyInputOptionalPackets = [];
    this.textDecoder = new TextDecoder("utf-8");
    this.textEncoder = new TextEncoder();
    this.cnterRunAtom = 0;
    this.dataSubscribers = {};
    this.taskManager = null;
    this.sessionSamplesCtxs = {};
    this.hostStatusMonitor = null;
    this.pointCloudRefs = Array(12).fill(0);
    this.pointCloudRestore = null;
    this.videoBuffer = null;
    this.timelineManager = null;
    this.onlineSession = null;
    this.secondProfileManager = null;
    this.roundTripTimeManager = null;

    this.ReceiveAnyPacketTimeout = 1000; // ms
    this.RetryTimeout = 3000; // ms
    this.PacketMagicHead = [ 55, 13, 34, 8, 233, 144, 22 ];
    this.MaxStringLength = 67108864; // 64M
    this.OptionalBufferRange = 300; // ms
    this.SendBufferThreshold = 65536; // 64k
    this.AgencyOutputWaitTimeout = 5500; // ms
    this.CnterRunInterval = 50; // ms
    this.SubscriberIdleRequestInterval = 200; // ms
    this.SubscriberDequeueTimeout = 3000; // ms
    this.SubscriberDequeueMinInterval = 15; // ms
    this.SegmentSizeHint = 16384; // 16k
    this.SegmentSizeTolerance = 10; // %
    this.SegmentRestoreTimeout = 4000; // ms
    this.MaxAgencyInputPackSize = 10000; // bytes, 应小于SegmentSizeHint
    this.MinZipSize = 200; // bytes
    this.MaxZipSize = 1000000; // bytes  
    this.MinZipRatio = 0.1;

    this.TimeWithSession = null;
    this.TwoStrings = null;
    this.DequeueDataFromNativeOutput = null;
    this.EnqueueDataToNativeInput = null;
    this.StringArray = null;
    this.DeviceStatusDetail = null;
    this.GetAllDeviceStatusOutput = null;
    this.StringDoubleMap = null;
    this.OptionalUInt = null;
    this.GetChannelStatusOutput = null;
    this.AudioDriverInfo = null;
    this.GetAudioDriversOutput = null;
    this.AudioDeviceInfo = null;
    this.GetAudioDevicesOutput = null;
    this.IntArray = null;
    this.TwoNumber = null;
    this.BusChannelInfo = null;
    this.GetBusChannelsInfoOutput = null;
    this.GetChannelStatusInput = null;
    this.StringAndOptionalUInt = null;
    this.BusDeviceID = null;
    this.BusDeviceInfo = null;
    this.GetBusDevicesOutput = null;
    this.IntAndUInt = null;
    this.BusMessageInfo = null;
    this.OptionalDouble = null;
    this.OptionalInt = null;
    this.BusProtocolFileID = null;
    this.GetBusProtocolFileIDListOutput = null;
    this.BusSignalInfo = null;
    this.StringStringMap = null;
    this.IndependentTimeInfo = null;
    this.Timestamp = null;
    this.GetChannelLatestTimestampsOutput = null;
    this.StringBoolMap = null;
    this.ConsoleClassInfo = null;
    this.GetConsoleClassTableOutput = null;
    this.GetRelatedModulesConfigStatusOutput = null;
    this.CPUTimeModel = null;
    this.DeviceClassInfo = null;
    this.GetDeviceClassTableOutput = null;
    this.EventData = null;
    this.EventInfo = null;
    this.PosixTimeModel = null;
    this.GetGraphDataInput = null;
    this.GraphDefinition = null;
    this.GraphData = null;
    this.GraphicCardInfo = null;
    this.GetGraphicCardInfosOutput = null;
    this.GetDateTimeInput = null;
    this.LogMessage = null;
    this.GetLogMessagesOutput = null;
    this.GetModuleChildConfigStatusOutput = null;
    this.GetModuleConfigStatusOutput = null;
    this.ModuleDetails = null;
    this.NativeClassInfo = null;
    this.GetNativeClassTableOutput = null;
    this.PluginPackInfo = null;
    this.GetPluginThirdPartyNoticesOutput = null;
    this.CameraInfo = null;
    this.GetPreviewJpegInput = null;
    this.GetPreviewJpegOutput = null;
    this.ProcessorClassInfo = null;
    this.GetProcessorClassTableOutput = null;
    this.SessionFilterFlags = null;
    this.GetSessionFilterTableOutput = null;
    this.StringAndBool = null;
    this.SignalTreeNode = null;
    this.GetSignalTreeOutput = null;
    this.TaskClassInfo = null;
    this.GetTaskClassTableOutput = null;
    this.IntSize = null;
    this.VideoInputMode = null;
    this.VideoChannelInfo = null;
    this.GetVideoChannelsInfoOutput = null;
    this.VideoDeviceID = null;
    this.VideoOutputMode = null;
    this.VideoDeviceInfo = null;
    this.GetVideoDevicesOutput = null;
    this.IntRect = null;
    this.GetVideoFrameImageInput = null;
    this.CommonImage = null;
    this.GetVideoFrameImageOutput = null;
    this.GetVideoFrameThumbnailInput = null;
    this.IntAndDouble = null;
    this.BusMessageSample = null;
    this.BusSignalValue = null;
    this.ParseBusMessageOutput = null;
    this.PublishDataInput = null;
    this.BusMessage = null;
    this.SendRawDataInput = null;
    this.IntAndString = null;
    this.StringAndDouble = null;
    this.SetSessionPropertiesInput = null;
    this.StartOfflineOnlineInput = null;
    this.StartRemoteInput = null;
    this.StartRemoteWithControllerInput = null;
    this.StartReplayInput = null;
    this.TwoBools = null;
    this.SubscribeDataInput = null;
    this.SwitchAppModeInput = null;
    this.OptionalULong = null;
    this.GPUDecoderTestResults = null;
    this.ThreeStrings = null;
    this.CallNativeFunctionInput = null;
    this.DataReferenceInfo = null;
    this.GenerateBusMessageInput = null;

    this.Protocol = {
        StartSession : 3,
        StopSession : 4,
        TransferStat : 6,
        MaskSeed : 7,
        Prompt : 10,
        Print : 11,
        Confirm : 12,
        InputNumber : 13,
        InputString : 14,
        SingleSelect : 15,
        MultiSelect : 16,
        Box : 21,
        TaskStart : 22,
        TaskProgress : 23,
        TaskResult : 24,
        CancelTask : 25,
        SetVideoTransfer : 26,
        DebugPrint : 108,
        SessionManualTrigger : 110,
        SessionBusMessage : 111,
        SessionVideoFrame : 112,
        SessionGeneralSample : 113,
        SessionSignal : 114,
        SessionPointCloudSlice : 115,
        SessionAudioFrame : 116,
        SessionSecondMosaic : 117,
        SecondProfile : 118,
        SessionBusMessages : 119,
        AgencyInput : 190,
        AgencyOutput : 191,
        SubscriberRequest : 192,
        SubscriberResponse : 193,
        AgencyInputs : 194,
        AgencyOutputs : 195,
        Segment : 200,
        RoundTripTime : 201,
        RoundTripTest : 202,
        Zipped : 203,
    };

    this.isConnected = () => {
        return this.connected && this.lastReceiveTime != null;
    };

    this.reconnect = () => {
        if (this.connectionMode == appoxEnums.ConnectionMode.Worker || this.connectionMode == appoxEnums.ConnectionMode.Bridge) {
            this.handleDisconnection(false);
        }
    };

    this.onTimerTick = () => {

        // Object initialization /////////////////////////////////////////////////////////

        this.ensureVideoBuffer();
        this.ensureTimelineManager();
        if (this.taskManager == null) {
            this.taskManager = new AppoxI7TaskManager();
        }
        if (this.hostStatusMonitor == null) {
            this.hostStatusMonitor = new AppoxI7HostStatusMonitor();
        }
        if (this.pointCloudRestore == null) {
            this.pointCloudRestore = new AppoxI7PointCloudRestore();
        }
        if (this.secondProfileManager == null) {
            this.secondProfileManager = new AppoxI7SecondProfileManager();
        }
        if (this.roundTripTimeManager == null) {
            this.roundTripTimeManager = new AppoxI7RoundTripTimeManager();
        }

        // Connection management /////////////////////////////////////////////////////////

        if (this.socket == null && (this.recreateTime == null || Date.now() > this.recreateTime)) {
            this.recreateTime = null;
            if (this.connectionMode == appoxEnums.ConnectionMode.Worker) {
                this.socket = new WebSocket("ws://" + this.ip + ":" + this.workerPort);
            }
            else if (this.connectionMode == appoxEnums.ConnectionMode.Bridge) {
                this.socket = new WebSocket("ws://" + this.ip + ":" + this.bridgePort + "/?consolePort=" + this.bridgeConsolePort);
            }
            this.socket.binaryType = 'arraybuffer';
            this.socket.addEventListener("open", this.onSocketOpen);
            this.socket.addEventListener("close", this.onSocketClose);
            this.socket.addEventListener("error", this.onSocketError);
            this.socket.addEventListener("message", this.onMessageReceived);
        }

        if (this.lastReceiveTime != null && Date.now() - this.lastReceiveTime > this.ReceiveAnyPacketTimeout) {
            appoxCallback.onLog("Web socket timeout.", appoxEnums.LogLevel.Warning);
            this.handleDisconnection(false);
        }

        // Handle agency input packets /////////////////////////////////////////////////////////

        this.processAgencyInputQueue(this.agencyInputRequiredPacketsSrc, this.agencyInputRequiredPackets);
        this.processAgencyInputQueue(this.agencyInputOptionalPacketsSrc, this.agencyInputOptionalPackets);

        let t0 = Date.now();
        while (this.agencyInputOptionalPackets.length > 0) {
            if (t0 - this.agencyInputOptionalPackets[0].time > this.OptionalBufferRange) {
                this.agencyInputOptionalPackets.shift();
            }
            else break;
        }

        while (this.isConnected() && this.socket.bufferedAmount < this.SendBufferThreshold) {
            let target = null;
            let isRequired = false;
            if (this.agencyInputRequiredPackets.length > 0) {
                target = this.agencyInputRequiredPackets[0].packet;
                this.agencyInputRequiredPackets.shift();
                isRequired = true;
            }
            if (target == null && this.agencyInputOptionalPackets.length > 0) {
                target = this.agencyInputOptionalPackets[0].packet;
                this.agencyInputOptionalPackets.shift();
            }
            if (target == null) break;
            try {
                this.socket.send(this.generateRawPacket(target));
            } catch (e) {
                if (isRequired) {
                    appoxCallback.onLog("Web socket send failed.", appoxEnums.LogLevel.Warning);
                    this.handleDisconnection(false);
                }
            }
        }

        // Update data subscribers /////////////////////////////////////////////////////////

        for (let key in this.dataSubscribers)
        {
            let subscriber = this.dataSubscribers[key];
            if (subscriber.closed) delete this.dataSubscribers[key];
            else subscriber.idleRequest();
        }

        // Reset data while mode, data layer changed /////////////////////////////////////////////////////////

        if (this.hostStatusMonitor.shouldResetData()) {
            this.sessionSamplesCtxs = {};
            this.videoBuffer.reset();
            appoxCallback.onResetData();
        }

        // Dequeue point cloud samples /////////////////////////////////////////////////////////

        this.pointCloudRestore.updateWithReferences(this.pointCloudRefs);

        let pointCloudSamples = this.pointCloudRestore.dequeue();
        pointCloudSamples.forEach(sample => {
            this.addSampleToContext(sample);
            this.secondProfileManager.onAvailablePointCloudSample(sample);
        });

        // Handle new data /////////////////////////////////////////////////////////

        let newSamples = [];
        for (let key in this.sessionSamplesCtxs) {
            let ctx = this.sessionSamplesCtxs[key];
            let samples = ctx.dequeueSamples();
            for (let sample of samples) {
                newSamples.push(sample);
                if (sample instanceof AppoxI7JpegSample) this.videoBuffer.updateWithJpegSample(sample);
                else appoxCallback.onInputData(sample);
            }
        }
        let timelineLower = null;
        let timelineUpper = null;
        if (this.hostStatusMonitor.getAppMode() == appoxEnums.ApplicationMode.Online) {
            if (this.onlineSession != null && this.onlineSession.isValid) {
                for (let sample of newSamples) {
                    if (!sample.timestamp.session.equals(this.onlineSession)) continue;
                    if (timelineLower == null) timelineLower = sample.timeline;
                    else timelineLower = Math.min(timelineLower, sample.timeline);
                    if (timelineUpper == null) timelineUpper = sample.timeline;
                    else timelineUpper = Math.max(timelineUpper, sample.timeline);
                }
            }
        }
        else {
            for (let sample of newSamples) {
                if (timelineLower == null) timelineLower = sample.timeline;
                else timelineLower = Math.min(timelineLower, sample.timeline);
                if (timelineUpper == null) timelineUpper = sample.timeline;
                else timelineUpper = Math.max(timelineUpper, sample.timeline);
            }
        }
        if (timelineLower != null && timelineUpper != null) {
            this.timelineManager.updateWithSampleRange(timelineLower, timelineUpper);
        }

        // Dequeue second profile packs /////////////////////////////////////////////////////////
        let secondProfilePacks = this.secondProfileManager.dequeue();
        for (let pack of secondProfilePacks) {
            appoxCallback.onInputData(pack);
        }

        // Update stuffs /////////////////////////////////////////////////////////
        this.videoBuffer.update();
        this.timelineManager.updateBufferRangeAndInterest();
        this.roundTripTimeManager.update();
    };

    this.ensureTimelineManager = () => {
        if (this.timelineManager == null) {
            this.timelineManager = new AppoxI7TimelineManager();
        }
    }

    this.ensureVideoBuffer = () => {
        if (this.videoBuffer == null) {
            this.videoBuffer = new AppoxI7VideoBuffer();
        }
    }

    this.removeSocketEventListeners = () => {
        if (this.socket != null) {
            this.socket.removeEventListener("open", this.onSocketOpen);
            this.socket.removeEventListener("close", this.onSocketClose);
            this.socket.removeEventListener("error", this.onSocketError);
            this.socket.removeEventListener("message", this.onMessageReceived);
        }
    };

    this.onSocketOpen = (event) => {

        this.lastReceiveTime = Date.now();
        this.segmentRestore = null;
        this.connected = true;
        this.timelineManager.resetByConnected();
        this.pointCloudRefs = Array(12).fill(0);
        this.agencyInputRequiredPacketsSrc = [];
        this.agencyInputOptionalPacketsSrc = [];
        this.agencyInputRequiredPackets = [];
        this.agencyInputOptionalPackets = [];

        let maskSeed = Math.floor(Math.random() * 100000000);
        this.masks = new AppoxI7Masks();
        this.masks.updateSeed(maskSeed);
        this.socket.send(this.generateRawPacket(this.generateMaskSeedPacket(maskSeed)));

        appoxCallback.onConnectionChanged(true);
    };

    this.onSocketClose = (event) => {
        this.handleDisconnection(true);
        if (event.code == 1000 && event.reason != null && event.reason != "") {
            appoxCallback.onLog(event.reason, appoxEnums.LogLevel.Warning);
        }
    };

    this.onSocketError = (event) => {
        appoxCallback.onLog("Web socket connection failed.", appoxEnums.LogLevel.Warning);
    };

    this.onMessageReceived = (event) => {
        this.lastReceiveTime = Date.now();
        let packets = this.parseRawPacket(new Uint8Array(event.data));
        if (this.segmentRestore == null) {
            this.segmentRestore = new AppoxI7RemoteSegmentRestore();
        }
        packets = this.segmentRestore.restore(packets);
        packets.forEach(packet => {
            this.handlePacket(packet);
        });
    };

    this.handleDisconnection = (socketClosing) => {
        this.lastReceiveTime = null;
        if (this.socket != null) {
            this.removeSocketEventListeners();
            if (!socketClosing) {
                try {
                    this.socket.close();
                } catch (e) {}
            }
            this.socket = null;
        }
        this.recreateTime = Date.now() + this.RetryTimeout;
        if (this.connected) {
            this.connected = false;
            this.taskManager.onDisconnected();
            this.pointCloudRefs = Array(12).fill(0);
            this.commVersionMismatch = false;
            this.masks = null;
            this.randomMaskReceived = false;
            appoxCallback.onConnectionChanged(false);
        }
        for (let key in this.dataSubscribers) {
            this.dataSubscribers[key].close();
        }
    };

    this.parseRawPacket = (rawPacket) => {

        if (this.masks == null) return [];

        let packetList = [];
        let totalLength = rawPacket.length;
        let headIndex = 0;

        while (true) {

            if (totalLength - headIndex < 12) break;

            let matchHead = true;
            for (let i = 0; i < 7; i++) {
                if (rawPacket[headIndex + i] != this.PacketMagicHead[i]) {
                    if (i == 6 && rawPacket[headIndex + i] >= 21 && rawPacket[headIndex + i] < 30 && !this.commVersionMismatch) {
                        this.commVersionMismatch = true;
                        appoxCallback.onLog(appoxCallback.onGetAppLanguage() == appoxEnums.Language.Chinese ? "通讯协议版本不匹配。" : "Communication protocol version mismatch.", appoxEnums.LogLevel.Warning);
                        this.handleDisconnection(false);
                        return [];
                    }
                    else {
                        matchHead = false;
                        break;
                    }
                }
            }
            if (!matchHead) break;

            let maskID = rawPacket[7];
            let header = [rawPacket[headIndex + 8], rawPacket[headIndex + 9], rawPacket[headIndex + 10], rawPacket[headIndex + 11]];
            if (!this.masks.removeMask(header, maskID)) break;

            let binarySize = (header[1] << 16) | (header[2] << 8) | header[3];
            if (binarySize < 4) break;

            let binarySizeM4 = binarySize % 4;
            let alignBytes = binarySizeM4 == 0 ? 0 : (4 - binarySizeM4);
            let fullSize = 8 + binarySize + alignBytes;
            if (totalLength < headIndex + fullSize) break;

            let data = new Uint8Array(rawPacket.buffer, headIndex + 8, binarySize);
            if (this.masks.removeMask(data, maskID)) {
                data[1] = data[2] = data[3] = 0; // erase binary size
                packetList.push(data);
                if (maskID != 255) this.randomMaskReceived = true;
            }

            headIndex += fullSize;
        }

        if (headIndex != totalLength) {
            appoxCallback.onDebugMessage("Invalid raw packet received.", "AppoxJs");
        }
        return packetList;
    };

    this.handlePacket = (packet) => {

        if (packet.length < 4) return;

        let uint32View = new Uint32Array(packet.buffer, packet.byteOffset, 1);
        let protocol = uint32View[0];

        switch (protocol) {
            case this.Protocol.StartSession:
                this.handleStartSession();
                break;
            case this.Protocol.StopSession:
                if (packet.length > 4) this.handleStopSession(this.parseJson(packet, 4, null));
                else this.handleStopSession(null);
                break;
            case this.Protocol.TransferStat:
                this.handleTransferStat(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.Print:
                this.handlePrint(this.parseJson(packet, 4, null));
                    break;
            case this.Protocol.Confirm:
                this.handleConfirm(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.InputNumber:
                this.handleInputNumber(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.InputString:
                this.handleInputString(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.SingleSelect:
                this.handleSingleSelect(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.MultiSelect:
                this.handleMultiSelect(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.Box:
                this.handleBox(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.TaskStart:
                this.handleTaskStart(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.TaskProgress:
                this.handleTaskProgress(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.TaskResult:
                this.handleTaskResult(this.parseJson(packet, 4, null));
                break;                
            case this.Protocol.DebugPrint:
                this.handleDebugPrint(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.SessionManualTrigger:
                this.handleSessionManualTrigger(packet);
                break;
            case this.Protocol.SessionBusMessage:
                this.handleSessionBusMessage(packet);
                break;
            case this.Protocol.SessionBusMessages:
                this.handleSessionBusMessages(packet);
                break;
            case this.Protocol.SessionVideoFrame:
                this.handleSessionVideoFrame(packet);
                break;
            case this.Protocol.SessionGeneralSample:
                this.handleSessionGeneralSample(packet);
                break;
            case this.Protocol.SessionSignal:
                this.handleSessionSignal(packet);
                break;
            case this.Protocol.SessionPointCloudSlice:
                this.handleSessionPointCloudSlice(packet);
                break;
            case this.Protocol.SessionAudioFrame:
                this.handleSessionAudioFrame(packet);
                break;
            case this.Protocol.SessionSecondMosaic:
                this.handleSessionSecondMosaic(packet);
                break;
            case this.Protocol.SecondProfile:
                this.handleSecondProfile(this.parseJson(packet, 4, null));
                break;
            case this.Protocol.AgencyOutput: {
                uint32View = new Uint32Array(packet.buffer, packet.byteOffset, 2);
                this.handleAgencyOutput(this.parseJson(packet, 8, uint32View[1]), packet);
                break;
            }
            case this.Protocol.AgencyOutputs:
                this.handleAgencyOutputs(packet);
                break;
            case this.Protocol.SubscriberResponse:
                uint32View = new Uint32Array(packet.buffer, packet.byteOffset, 2);
                this.handleSubscriberResponse(this.parseJson(packet, 8, uint32View[1]), uint32View[1], packet);
                break;
            case this.Protocol.RoundTripTest:
                if (this.isConnected()) this.socket.send(this.generateRawPacket(packet));
                break;
        }
    };

    this.parseJson = (packet, offset, size) => {
        let target = null;
        if (size == null) target = new Uint8Array(packet.buffer, packet.byteOffset + offset, packet.length - offset);
        else target = new Uint8Array(packet.buffer, packet.byteOffset + offset, size);
        return JSON.parse(this.textDecoder.decode(target));
    };

    this.generatePromptPacket = (text) => {
        let jsonBytes = this.textEncoder.encode(JSON.stringify({ Text: text }));
        let packet = new Uint8Array(4 + jsonBytes.length);
        let uint32View = new Uint32Array(packet.buffer, 0, 1);
        uint32View[0] = this.Protocol.Prompt;
        packet.set(jsonBytes, 4);
        return packet;
    };

    this.generateTaskCancelPacket = (guid) => {
        let jsonBytes = this.textEncoder.encode(JSON.stringify({ GUID: guid }));
        let packet = new Uint8Array(4 + jsonBytes.length);
        let uint32View = new Uint32Array(packet.buffer, 0, 1);
        uint32View[0] = this.Protocol.CancelTask;
        packet.set(jsonBytes, 4);
        return packet;
    };

    this.generateAgencyInputPacket = (func, id, data) => {
        let dataSize = data == null ? -1 : data.length;
        let jsonBytes = this.textEncoder.encode(JSON.stringify({
            F: func,
            I: id,
            S: dataSize,
        }));
        let packet = new Uint8Array(8 + jsonBytes.length + (dataSize > 0 ? dataSize : 0));
        let uint32View = new Uint32Array(packet.buffer, 0, 2);
        uint32View[0] = this.Protocol.AgencyInput;
        uint32View[1] = jsonBytes.length;
        packet.set(jsonBytes, 8);
        if (dataSize > 0) packet.set(data, 8 + jsonBytes.length);
        return packet;
    };

    this.generateSubscriberRequestPacket = (guid, closeRequest, checkOnly) => {
        let jsonBytes = this.textEncoder.encode(JSON.stringify({ GUID: guid, CloseRequest: closeRequest, CheckOnly: checkOnly }));
        let packet = new Uint8Array(4 + jsonBytes.length);
        let uint32View = new Uint32Array(packet.buffer, 0, 1);
        uint32View[0] = this.Protocol.SubscriberRequest;
        packet.set(jsonBytes, 4);
        return packet;
    }

    this.generateSetVideoTransferPacket = (channel, enable) => {
        let jsonBytes = this.textEncoder.encode(JSON.stringify({ Channel: channel, Enable: enable }));
        let packet = new Uint8Array(4 + jsonBytes.length);
        let uint32View = new Uint32Array(packet.buffer, 0, 1);
        uint32View[0] = this.Protocol.SetVideoTransfer;
        packet.set(jsonBytes, 4);
        return packet;
    }

    this.generateSegmentPackets = (packet, protocol) => {

        let maxSegmentSize = this.SegmentSizeHint * (1.0 + 0.01 * this.SegmentSizeTolerance);
        if (packet.length <= maxSegmentSize) return [ packet ];

        let totalSegments = Math.ceil(packet.length / this.SegmentSizeHint);
        if (packet.length / (totalSegments - 1) < maxSegmentSize) totalSegments--;

        let segmentSize = Math.ceil(packet.length / totalSegments);
        let uuid = this.generateUuidV4().replaceAll("-", "");

        let segments = [];
        let segmentHeadIndex = 0;
        for (let i = 0; i < totalSegments; i++) {
            let thisSegmentSize = Math.min(segmentSize, packet.length - segmentHeadIndex);
            let jsonBytes = this.textEncoder.encode(JSON.stringify({ I: uuid, P: protocol, S: packet.length, N: totalSegments, X: i, Z: thisSegmentSize }));
            let packet = new Uint8Array(8 + jsonBytes.length + thisSegmentSize);
            let uint32View = new Uint32Array(packet.buffer, 0, 2);
            uint32View[0] = this.Protocol.Segment;
            uint32View[1] = jsonBytes.length;
            packet.set(jsonBytes, 8);
            packet.set(new Uint8Array(packet.buffer, packet.byteOffset + segmentHeadIndex, thisSegmentSize), 8 + jsonBytes.length);
            segments.push(packet);
            segmentHeadIndex += thisSegmentSize;
        }
        return segments;
    };

    this.generateRoundTripTimePacket = (time) => {
        let jsonBytes = this.textEncoder.encode(JSON.stringify({ Milliseconds: time }));
        let packet = new Uint8Array(4 + jsonBytes.length);
        let uint32View = new Uint32Array(packet.buffer, 0, 1);
        uint32View[0] = this.Protocol.RoundTripTime;
        packet.set(jsonBytes, 4);
        return packet;
    }

    this.generateMaskSeedPacket = (maskSeed) => {
        let jsonBytes = this.textEncoder.encode(JSON.stringify({ MaskSeed: maskSeed }));
        let packet = new Uint8Array(4 + jsonBytes.length);
        let uint32View = new Uint32Array(packet.buffer, 0, 1);
        uint32View[0] = this.Protocol.MaskSeed;
        packet.set(jsonBytes, 4);
        return packet;
    }

    this.generateRawPacket = (packet) => {

        if (this.masks == null) return null;

        let packetLength = packet.length;
        if (packetLength < 4 || packetLength >= 65536 * 256) return null;
        if (packet[1] != 0 || packet[2] != 0 || packet[3] != 0) return null;

        let packetLengthM4 = packetLength % 4;
        let alignBytes = packetLengthM4 == 0 ? 0 : (4 - packetLengthM4);
        let rawPacket = new Uint8Array(8 + packetLength + alignBytes);

        rawPacket.set(this.PacketMagicHead, 0);
        rawPacket.set(packet, 8);
        rawPacket[9] = (packetLength >> 16) & 0xFF;
        rawPacket[10] = (packetLength >> 8) & 0xFF;
        rawPacket[11] = packetLength & 0xFF;

        let maskID = this.randomMaskReceived ? (Math.floor(Math.random() * 100000000) % 255) : 255;
        rawPacket[7] = this.masks.addMask(rawPacket.subarray(8, packetLength + 8), maskID);
        return rawPacket;
    };

    this.getServerHeader = () => {
        return appoxCallback.onGetAppLanguage() == appoxEnums.Language.Chinese ? "(服务器) " : "(Server) ";
    };

    this.handleStartSession = () => {
        this.sessionSamplesCtxs = {};
        this.pointCloudRestore.reset();
        this.videoBuffer.reset();
        this.timelineManager.resetByStartSession();
        this.secondProfileManager.reset();
        this.secondProfileManager.setEnable(true);
        this.onlineSession = null;
        appoxCallback.onStartSession();
    };

    this.handleStopSession = (input) => {
        this.secondProfileManager.setEnable(false);
        if (input != null && input.RecordedSession != null) {
            let session = new AppoxSessionIdentifier(input.RecordedSession);
            if (session.isValid) appoxCallback.onEditRecordedSession(session);
        }
        else {
            appoxCallback.onStopSession();
        }
    };

    this.handleTransferStat = (input) => {
        let table = {};
        
        for (let elem of input.Elements) {
            let type = null;

            switch (elem.Protocol) {
                case this.Protocol.BusMessage:
                case this.Protocol.SessionBusMessage:
                case this.Protocol.SessionBusMessages:
                    type = appoxEnums.TransferStreamType.BusMessage;
                    break;
                case this.Protocol.VideoFrame:
                case this.Protocol.SessionVideoFrame:
                case this.Protocol.SessionSecondMosaic:
                    type = appoxEnums.TransferStreamType.VideoFrame;
                    break;
                case this.Protocol.AudioFrame:
                case this.Protocol.SessionAudioFrame:
                    type = appoxEnums.TransferStreamType.AudioFrame;
                    break;
                case this.Protocol.Signal:
                case this.Protocol.SessionSignal:
                    type = appoxEnums.TransferStreamType.Signal;
                    break;
                case this.Protocol.GeneralSample:
                case this.Protocol.SessionGeneralSample:
                case this.Protocol.ManualTrigger:
                case this.Protocol.SessionManualTrigger:
                    type = appoxEnums.TransferStreamType.GeneralSample;
                    break;
                case this.Protocol.PointCloud:
                case this.Protocol.SessionPointCloudSlice:
                    type = appoxEnums.TransferStreamType.PointCloud;
                    break;
                case this.Protocol.DebugPrint:
                case this.Protocol.AgencyInput:
                case this.Protocol.AgencyInputs:
                case this.Protocol.AgencyOutput:
                case this.Protocol.AgencyOutputs:
                case this.Protocol.SubscriberRequest:
                case this.Protocol.SubscriberResponse:
                    type = appoxEnums.TransferStreamType.Function;
                    break;
            }
            
            if (type != null) {
                if (table[type]) {
                    table[type].successBytesPerSecond += BigInt(elem.SuccessBytesPerSecond);
                    table[type].failBytesPerSecond += BigInt(elem.FailBytesPerSecond);
                } else {
                    table[type] = new AppoxTransferStatistics(
                        BigInt(elem.SuccessBytesPerSecond),
                        BigInt(elem.FailBytesPerSecond)
                    );
                }
            }
        }
        
        appoxCallback.onTransferStatistics(table);
    };

    this.handlePrint = (input) => {
        let message = input.Message;
        if (message.startsWith("[INFO] ")) appoxCallback.onLog(this.getServerHeader() + message.slice(7), appoxEnums.LogLevel.Info);
        else if (message.startsWith("[ERRR] ")) appoxCallback.onLog(this.getServerHeader() + message.slice(7), appoxEnums.LogLevel.Error);
        else if (message.startsWith("[WARN] ")) appoxCallback.onLog(this.getServerHeader() + message.slice(7), appoxEnums.LogLevel.Warning);
        else appoxCallback.onLog(this.getServerHeader() + message, appoxEnums.LogLevel.Info);
    };

    this.handleConfirm = (input) => {
        let message = input.Message;
        let defaultResult = input.Default !== undefined ? input.Default : 0; // 0: invalid, 1, true, 2: false
        let chinese = appoxCallback.onGetAppLanguage() == appoxEnums.Language.Chinese;
        if (defaultResult == 0) {
            appoxCallback.onConfirm(message, chinese ? "确认" : "Confirm").then((confirmed) => {
                let packet = this.generatePromptPacket(confirmed ? "o" : "x");
                let rawPacket = this.generateRawPacket(packet);
                if (this.isConnected()) this.socket.send(rawPacket);
            });
        }
        else {
            appoxCallback.onConfirmWithDefault(message, chinese ? "确认" : "Confirm", defaultResult == 1).then((confirmed) => {
                let packet = this.generatePromptPacket(confirmed ? "o" : "x");
                let rawPacket = this.generateRawPacket(packet);
                if (this.isConnected()) this.socket.send(rawPacket);
            });
        }
    };

    this.handleInputNumber = (input) => {
        let message = input.Message;
        let defaultNumber = input.Default;
        appoxCallback.onRequestInputNumber(message, defaultNumber).then((number) => {
            let packet = this.generatePromptPacket(number.toString());
            let rawPacket = this.generateRawPacket(packet);
            if (this.isConnected()) this.socket.send(rawPacket);
        });
    };

    this.handleInputString = (input) => {
        let message = input.Message;
        let defaultText = input.Default;
        appoxCallback.onRequestInputString(message, defaultText).then((text) => {
            let packet = this.generatePromptPacket(text);
            let rawPacket = this.generateRawPacket(packet);
            if (this.isConnected()) this.socket.send(rawPacket);
        });
    };

    this.handleSingleSelect = (input) => {
        let message = input.Message;
        let options = input.Options;
        let defaultSelectIndex = input.Default;
        appoxCallback.onRequestSingleSelect(message, options, defaultSelectIndex).then((selectedIndex) => {
            let packet = this.generatePromptPacket(selectedIndex.toString());
            let rawPacket = this.generateRawPacket(packet);
            if (this.isConnected()) this.socket.send(rawPacket);
        });
    };

    this.handleMultiSelect = (input) => {
        let message = input.Message;
        let options = input.Options;
        let defaultSelectIndices = input.Default;
        appoxCallback.onRequestMultiSelect(message, options, defaultSelectIndices).then((selectedIndices) => {
            let packet = this.generatePromptPacket(selectedIndices.join(","));
            let rawPacket = this.generateRawPacket(packet);
            if (this.isConnected()) this.socket.send(rawPacket);
        });
    };

    this.handleBox = (input) => {
        let message = input.Message;
        let isError = input.IsError;
        let chinese = appoxCallback.onGetAppLanguage() == appoxEnums.Language.Chinese;
        if (isError) appoxCallback.onAlert(this.getServerHeader() + message, chinese ? "错误" : "Error");
        else appoxCallback.onAlert(this.getServerHeader() + message, chinese ? "消息" : "Notice");
    };

    this.handleTaskStart = async (input) => {
        let guid = input.GUID;
        let taskClassID = input.TaskClassID;
        let taskConfig = input.TaskConfig;
        let taskTitle = input.TaskTitle;
        
        let callback = new AppoxI7RunTaskCallbackImpl(guid, taskConfig, this.socket);
        this.taskManager.register(guid, callback);
        await appoxCallback.onRunStandaloneTask(taskTitle, taskClassID, callback);
        this.taskManager.unregister(guid);
    };

    this.handleTaskProgress = (input) => {
        let guid = input.GUID;
        let progress = input.Progress;
        let description = input.Description;
        this.taskManager.onTaskProgress(guid, progress, description);
    };

    this.handleTaskResult = (input) => {
        let guid = input.GUID;
        let succeeded = input.Succeeded;
        let canceled = input.Canceled;
        let description = input.Description;
        let returnValue = input.ReturnValue;
        this.taskManager.onTaskResult(guid, succeeded, canceled, description, returnValue);
    };

    this.handleDebugPrint = (input) => {
        let message = input.M ?? input.Message;
        let source = input.S ?? input.Source;
        appoxCallback.onDebugMessage(message, "remote." + source);
    };

    this.ensureSessionSamplesContext = (session) => {
        let ctx = this.sessionSamplesCtxs[session];
        if (ctx == null) {
            ctx = new AppoxI7SessionSamplesContext();
            this.sessionSamplesCtxs[session] = ctx;
            ctx.ensureSessionTimeline(session);
        }
    }

    this.addSampleToContext = (sample) => {
        let ctx = this.sessionSamplesCtxs[sample.timestamp.session];
        if (ctx == null) {
            ctx = new AppoxI7SessionSamplesContext();
            this.sessionSamplesCtxs[sample.timestamp.session] = ctx;
        }
        ctx.addSample(sample);
    }

    this.handleSessionManualTrigger = (packet) => {
        let view = new DataView(packet.buffer, packet.byteOffset);
        let session = new AppoxSessionIdentifier();
        session.fromBitInt(view.getBigUint64(4, true));
        let offset = view.getFloat64(12, true);
        let states = [];
        for (let i = 0; i < 16; i++) {
            states.push(packet[20 + i] === 1);
        }
        let timestamp = new AppoxTimestamp(session, offset, appoxEnums.TimeOffsetSync.HostArrival, null);
        let sample = new AppoxManualTriggerSample(timestamp, null, states);
        this.addSampleToContext(sample);
        if (this.hostStatusMonitor.getAppMode() == appoxEnums.ApplicationMode.Online) {
            if (this.onlineSession != null && this.onlineSession.isValid && !this.onlineSession.equals(session)) {
                this.handleStopSession(null);
                this.handleStartSession();
            }
            this.onlineSession = session;
        }
    }

    this.handleSessionBusMessage = (packet) => {
        let view = new DataView(packet.buffer, packet.byteOffset);
        let session = new AppoxSessionIdentifier();
        session.fromBitInt(view.getBigUint64(4, true));
        let offset = view.getFloat64(12, true);
        let sync = this.convNumberToTimeOffsetSync(packet[24]);
        let timestamp = new AppoxTimestamp(session, offset, sync, null);
        let id = view.getUint32(20, true);
        let channel = packet[25];
        let type = this.convNumberToBusChannelType(packet[26]);
        let state = this.convNumberToBusRawDataState(packet[27]);
        let length = view.getUint16(28, true);
        let dataLength = view.getUint16(30, true);
        let data = dataLength === 0 ? new Uint8Array(0) : new Uint8Array(packet.buffer.slice(packet.byteOffset + 32, packet.byteOffset + 32 + dataLength));
        let sample = new AppoxBusMessageSample(timestamp, null, channel, length, id, data, type, state, null);
        this.addSampleToContext(sample);
        this.secondProfileManager.onAvailableBusData(sample);
    }

    this.handleSessionBusMessages = (packet) => {
        let curIndex = 4;
        while (true) {
            if (packet.length < curIndex + 28) break;
            let view = new DataView(packet.buffer, packet.byteOffset + curIndex);
            let session = new AppoxSessionIdentifier();
            session.fromBitInt(view.getBigUint64(0, true));
            let offset = view.getFloat64(8, true);
            let sync = this.convNumberToTimeOffsetSync(packet[curIndex + 20]);
            let timestamp = new AppoxTimestamp(session, offset, sync, null);
            let id = view.getUint32(16, true);
            let channel = packet[curIndex + 21];
            let type = this.convNumberToBusChannelType(packet[curIndex + 22]);
            let state = this.convNumberToBusRawDataState(packet[curIndex + 23]);
            let length = view.getUint16(24, true);
            let dataLength = view.getUint16(26, true);
            if (packet.length < curIndex + 28 + dataLength) break;
            let data = dataLength === 0 ? new Uint8Array(0) : new Uint8Array(packet.buffer.slice(packet.byteOffset + curIndex + 28, packet.byteOffset + curIndex + 28 + dataLength));
            let sample = new AppoxBusMessageSample(timestamp, null, channel, length, id, data, type, state, null);
            this.addSampleToContext(sample);
            this.secondProfileManager.onAvailableBusData(sample);
            curIndex += 28 + dataLength;
        }
    }

    this.handleSessionVideoFrame = (packet) => {
        let view = new DataView(packet.buffer, packet.byteOffset);
        let session = new AppoxSessionIdentifier();
        session.fromBitInt(view.getBigUint64(4, true));
        let offset = view.getFloat64(12, true);
        let sync = this.convNumberToTimeOffsetSync(packet[57]);
        let cpuTick = view.getBigUint64(24, true);
        let hostPosix = view.getBigUint64(32, true);
        let guestPosix = view.getBigUint64(40, true);
        let gnssPosix = view.getBigUint64(48, true);
        let timeInfo = null;
        if (cpuTick !== 0n || hostPosix !== 0n || guestPosix !== 0n || gnssPosix !== 0n) {
            timeInfo = new AppoxIndependentTimeInfo(cpuTick, hostPosix, guestPosix, gnssPosix);
        }
        let timestamp = new AppoxTimestamp(session, offset, sync, timeInfo);
        let channel = packet[56];
        let jpegLength = view.getUint32(20, true);
        let jpeg = jpegLength === 0 ? new Uint8Array(0) : new Uint8Array(packet.buffer.slice(packet.byteOffset + 58, packet.byteOffset + 58 + jpegLength));
        let sample = new AppoxI7JpegSample(timestamp, null, channel, jpeg);
        this.addSampleToContext(sample);
        this.secondProfileManager.onAvailableVideoData(sample);
    }

    this.handleSessionGeneralSample = (packet) => {
        let view = new DataView(packet.buffer, packet.byteOffset);
        let elemCount = view.getUint32(4, true);
        let session = new AppoxSessionIdentifier();
        session.fromBitInt(view.getBigUint64(8, true));
        let offset = view.getFloat64(16, true);
        let cpuTick = view.getBigUint64(32, true);
        let hostPosix = view.getBigUint64(40, true);
        let guestPosix = view.getBigUint64(48, true);
        let gnssPosix = view.getBigUint64(56, true);
        let timeInfo = null;
        if (cpuTick !== 0n || hostPosix !== 0n || guestPosix !== 0n || gnssPosix !== 0n) {
            timeInfo = new AppoxIndependentTimeInfo(cpuTick, hostPosix, guestPosix, gnssPosix);
        }
        let sync = this.convNumberToTimeOffsetSync(packet[64]);
        let timestamp = new AppoxTimestamp(session, offset, sync, timeInfo);
        let protocolLength = view.getUint32(24, true);
        let numberOfSignificants = view.getInt32(28, true);
        let channel = packet[65] === 1 ? view.getUint16(66, true) : null;
        let elemMeta = new Uint8Array(packet.buffer, packet.byteOffset + 72, elemCount);
        let doubleCount = 0, intCount = 0, byteCount = 0, textCount = 0;
        for (let i = 0; i < elemCount; i++) {
            switch (elemMeta[i]) {
                case 1: byteCount++; break;
                case 2: intCount++; break;
                case 3: doubleCount++; break;
                case 4: textCount++; break;
            }
        }
        let metaSize = ((elemCount + 7) & 0xfffffff8);
        let posDouble = 72 + metaSize;
        let posInt = posDouble + doubleCount * 8;
        let posTextLength = posInt + intCount * 4;
        let posByte = posTextLength + textCount * 2;
        let posProtocol = posByte + byteCount;
        let posTextUTF8 = posProtocol + protocolLength;
        let protocol = this.textDecoder.decode(new Uint8Array(packet.buffer, packet.byteOffset + posProtocol, protocolLength));
        let elems = [];
        let curDouble = posDouble;
        let curInt = posInt;
        let curTextLength = posTextLength;
        let curByte = posByte;
        let curTextUTF8 = posTextUTF8;
        for (let i = 0; i < elemCount; i++) {
            switch (elemMeta[i]) {
                case 1: // byte
                    elems.push(packet[curByte++]);
                    break;
                case 2: // int
                    elems.push(view.getInt32(curInt, true));
                    curInt += 4;
                    break;
                case 3: // double
                    elems.push(view.getFloat64(curDouble, true));
                    curDouble += 8;
                    break;
                case 4: // text
                    let textLength = view.getUint16(curTextLength, true);
                    curTextLength += 2;
                    let text = this.textDecoder.decode(new Uint8Array(packet.buffer, packet.byteOffset + curTextUTF8, textLength));
                    elems.push(text);
                    curTextUTF8 += textLength;
                    break;
                default:
                    elems.push(null);
                    break;
            }
        }
        let gSample = new AppoxGeneralSample(timestamp, null, protocol, channel, elems, numberOfSignificants);
        this.addSampleToContext(gSample);
        this.secondProfileManager.onAvailableGeneralSample(gSample);
    }

    this.handleSessionSignal = (packet) => {
        let view = new DataView(packet.buffer, packet.byteOffset);
        let session = new AppoxSessionIdentifier();
        session.fromBitInt(view.getBigUint64(12, true));
        let idLength = view.getUint32(4, true);
        let signalCount = view.getUint32(8, true);
        let posDoubles = 20;
        let posBytes = posDoubles + signalCount * 16;
        let posID = posBytes + signalCount;
        let signalID = this.textDecoder.decode(new Uint8Array(packet.buffer, packet.byteOffset + posID, idLength));
        let signals = new Array(signalCount);
        for (let i = 0; i < signalCount; i++) {
            let offset = view.getFloat64(posDoubles, true);
            posDoubles += 8;
            let val = view.getFloat64(posDoubles, true);
            posDoubles += 8;
            let sync = this.convNumberToTimeOffsetSync(packet[posBytes++]);
            signals[i] = new AppoxSignal(offset, sync, val);
        }
        let signalsData = new AppoxSignalsData(session, signalID, signals);
        appoxCallback.onInputData(signalsData);
    }

    this.handleSessionPointCloudSlice = (packet) => {
        let view = new DataView(packet.buffer, packet.byteOffset);
        let session = new AppoxSessionIdentifier();
        session.fromBitInt(view.getBigUint64(4, true));
        let offset = view.getFloat64(12, true);
        let sourcePointCount = view.getInt32(24, true);
        let cpuTick = view.getBigUint64(28, true);
        let hostPosix = view.getBigUint64(36, true);
        let guestPosix = view.getBigUint64(44, true);
        let gnssPosix = view.getBigUint64(52, true);
        let timeInfo = null;
        if (cpuTick !== 0n || hostPosix !== 0n || guestPosix !== 0n || gnssPosix !== 0n) {
            timeInfo = new AppoxIndependentTimeInfo(cpuTick, hostPosix, guestPosix, gnssPosix);
        }
        let sync = this.convNumberToTimeOffsetSync(packet[60]);
        let channel = packet[61];
        let withIntensities = packet[62] === 1;
        let sliceBytes = packet.length - 63;
        let data = sliceBytes > 0 ? new Uint8Array(packet.buffer.slice(packet.byteOffset + 63, packet.byteOffset + 63 + sliceBytes)) : new Uint8Array(0);
        let slice = new AppoxI7PointCloudSlice(channel, session, offset, sourcePointCount, sync, timeInfo, withIntensities, data);
        this.pointCloudRestore.updateWithSlice(slice, this.pointCloudRefs);
    }

    this.handleSessionAudioFrame = (packet) => {
        let view = new DataView(packet.buffer, packet.byteOffset);
        let session = new AppoxSessionIdentifier();
        session.fromBitInt(view.getBigUint64(4, true));
        let offset = view.getFloat64(12, true);
        let timestamp = new AppoxTimestamp(session, offset, appoxEnums.TimeOffsetSync.HostArrival, null);
        let sampleCount = view.getUint32(20, true);
        let data = new Int16Array(sampleCount);
        let srcData = new Int16Array(packet.buffer, packet.byteOffset + 24, sampleCount);
        data.set(srcData);
        let sample = new AppoxAudioSample(timestamp, null, data);
        this.addSampleToContext(sample);
        this.secondProfileManager.onAvailableAudioData(sample);
    }

    this.handleSessionSecondMosaic = (packet) => {
        let view = new DataView(packet.buffer, packet.byteOffset);
        let session = new AppoxSessionIdentifier();
        session.fromBitInt(view.getBigUint64(8, true));
        let second = view.getInt32(4, true);
        let mosaicHeight = view.getUint16(16, true);
        let channel = view.getUint16(18, true);
        let sampleCount = view.getUint16(20, true);
        let vgaHeight = view.getUint16(22, true);
        if (vgaHeight == 0) return;
        let offsets = new Array(sampleCount);
        let mosaics = new Array(sampleCount);
        let posOffsets = 24;
        let posMosaics = posOffsets + 8 * sampleCount;
        for (let i = 0; i < sampleCount; i++) {
            offsets[i] = view.getFloat64(posOffsets, true);
            posOffsets += 8;
            let image = new AppoxCommonImage(8, mosaicHeight, false, false, 8 * 3, new Uint8Array(8 * mosaicHeight * 3));
            let imageData = image.data;
            for (let v = 0; v < mosaicHeight; v++) {
                let rowIndex = v * image.rowBytes;
                for (let u = 0; u < 8; u++) {
                    let cellIndex = rowIndex + u * 3;
                    let pixel = view.getUint16(posMosaics, true);
                    posMosaics += 2;
                    imageData[cellIndex] = (pixel & 0x001f) << 3;
                    imageData[cellIndex + 1] = (pixel & 0x07e0) >> 3;
                    imageData[cellIndex + 2] = (pixel & 0xf800) >> 8;
                }
            }
            mosaics[i] = image;
        }
        let pack = new AppoxI7SecondMosaicPack(channel, session, second, mosaicHeight, vgaHeight, offsets, mosaics);
        this.videoBuffer.updateWithSecondMosaicPack(pack);
    }

    this.handleSecondProfile = (input) => {
        this.secondProfileManager.onSecondProfileProto(input);
    }

    this.handleAgencyOutput = (input, packet) => {
        let func = input.F ?? input.Function;
        let id = input.I ?? input.ID;
        let dataSize = input.S ?? input.DataSize;
        if (!this.agencyCallCtx.has(func)) return;
        let table = this.agencyCallCtx.get(func);
        if (!table.has(id)) return;
        let ctx = table.get(id);
        let data = null;
        if (dataSize > 0) data = new Uint8Array(packet.buffer, packet.byteOffset + packet.length - dataSize, dataSize);
        else if (dataSize == 0) data = new Uint8Array(0);
        ctx.setOutput(data);
    };

    this.handleAgencyOutputs = (packet) => {
        let curIndex = 4;
        while (true) {
            if (packet.length < curIndex + 2) break;
            let jsonSize = new DataView(packet.buffer, packet.byteOffset + curIndex, 2).getUint16(0, true);
            if (packet.length < curIndex + 2 + jsonSize) break;
            let input = this.parseJson(packet, curIndex + 2, jsonSize);
            let func = input.F ?? input.Function;
            let id = input.I ?? input.ID;
            let dataSize = input.S ?? input.DataSize;
            if (this.agencyCallCtx.has(func)) {
                let table = this.agencyCallCtx.get(func);
                if (table.has(id)) {
                    let ctx = table.get(id);
                    let data = null;
                    if (dataSize > 0) {
                        if (packet.length < curIndex + 2 + jsonSize + dataSize) break;
                        data = new Uint8Array(packet.buffer, packet.byteOffset + curIndex + 2 + jsonSize, dataSize);
                    } else if (dataSize == 0) {
                        data = new Uint8Array(0);
                    }
                    ctx.setOutput(data);
                }
            }
            curIndex += 2 + jsonSize + (dataSize > 0 ? dataSize : 0);
        }
    }

    this.handleSubscriberResponse = (input, inputJsonSize, packet) => {

        let guid = input.GUID;
        let isClosed = input.IsClosed;
        let dataSizes = input.DataSizes;

        let targetSubscriber = this.dataSubscribers[guid];
        if (targetSubscriber == null) return;

        let dataArray = [];
        if (dataSizes != null) {
            let curIndex = 8 + inputJsonSize;
            for (let i = 0; i < dataSizes.length; i++ ) {
                let curSize = dataSizes[i];
                if (curSize < 0) continue;
                let data = null;
                if (curSize == 0) data = new Uint8Array(0);
                else data = new Uint8Array(packet.buffer, packet.byteOffset + curIndex, curSize);
                dataArray.push(data);
                curIndex += curSize;
            }
        }

        targetSubscriber.onResponse(isClosed, dataArray);
    };

    this.initializeProtobuf = (protoFilePath) => {
        return new Promise((resolve) => {
            protobuf.load(protoFilePath, (err, root) => {
                this.TimeWithSession = root.lookup("TimeWithSession");
                this.TwoStrings = root.lookup("TwoStrings");
                this.DequeueDataFromNativeOutput = root.lookup("DequeueDataFromNativeOutput");
                this.EnqueueDataToNativeInput = root.lookup("EnqueueDataToNativeInput");
                this.StringArray = root.lookup("StringArray");
                this.DeviceStatusDetail = root.lookup("DeviceStatusDetail");
                this.GetAllDeviceStatusOutput = root.lookup("GetAllDeviceStatusOutput");
                this.StringDoubleMap = root.lookup("StringDoubleMap");
                this.OptionalUInt = root.lookup("OptionalUInt");
                this.GetChannelStatusOutput = root.lookup("GetChannelStatusOutput");
                this.AudioDriverInfo = root.lookup("AudioDriverInfo");
                this.GetAudioDriversOutput = root.lookup("GetAudioDriversOutput");
                this.AudioDeviceInfo = root.lookup("AudioDeviceInfo");
                this.GetAudioDevicesOutput = root.lookup("GetAudioDevicesOutput");
                this.IntArray = root.lookup("IntArray");
                this.TwoNumber = root.lookup("TwoNumber");
                this.BusChannelInfo = root.lookup("BusChannelInfo");
                this.GetBusChannelsInfoOutput = root.lookup("GetBusChannelsInfoOutput");
                this.GetChannelStatusInput = root.lookup("GetChannelStatusInput");
                this.StringAndOptionalUInt = root.lookup("StringAndOptionalUInt");
                this.BusDeviceID = root.lookup("BusDeviceID");
                this.BusDeviceInfo = root.lookup("BusDeviceInfo");
                this.GetBusDevicesOutput = root.lookup("GetBusDevicesOutput");
                this.IntAndUInt = root.lookup("IntAndUInt");
                this.BusMessageInfo = root.lookup("BusMessageInfo");
                this.OptionalDouble = root.lookup("OptionalDouble");
                this.OptionalInt = root.lookup("OptionalInt");
                this.BusProtocolFileID = root.lookup("BusProtocolFileID");
                this.GetBusProtocolFileIDListOutput = root.lookup("GetBusProtocolFileIDListOutput");
                this.BusSignalInfo = root.lookup("BusSignalInfo");
                this.StringStringMap = root.lookup("StringStringMap");
                this.IndependentTimeInfo = root.lookup("IndependentTimeInfo");
                this.Timestamp = root.lookup("Timestamp");
                this.GetChannelLatestTimestampsOutput = root.lookup("GetChannelLatestTimestampsOutput");
                this.StringBoolMap = root.lookup("StringBoolMap");
                this.ConsoleClassInfo = root.lookup("ConsoleClassInfo");
                this.GetConsoleClassTableOutput = root.lookup("GetConsoleClassTableOutput");
                this.GetRelatedModulesConfigStatusOutput = root.lookup("GetRelatedModulesConfigStatusOutput");
                this.CPUTimeModel = root.lookup("CPUTimeModel");
                this.DeviceClassInfo = root.lookup("DeviceClassInfo");
                this.GetDeviceClassTableOutput = root.lookup("GetDeviceClassTableOutput");
                this.EventData = root.lookup("EventData");
                this.EventInfo = root.lookup("EventInfo");
                this.PosixTimeModel = root.lookup("PosixTimeModel");
                this.GetGraphDataInput = root.lookup("GetGraphDataInput");
                this.GraphDefinition = root.lookup("GraphDefinition");
                this.GraphData = root.lookup("GraphData");
                this.GraphicCardInfo = root.lookup("GraphicCardInfo");
                this.GetGraphicCardInfosOutput = root.lookup("GetGraphicCardInfosOutput");
                this.GetDateTimeInput = root.lookup("GetDateTimeInput");
                this.LogMessage = root.lookup("LogMessage");
                this.GetLogMessagesOutput = root.lookup("GetLogMessagesOutput");
                this.GetModuleChildConfigStatusOutput = root.lookup("GetModuleChildConfigStatusOutput");
                this.GetModuleConfigStatusOutput = root.lookup("GetModuleConfigStatusOutput");
                this.ModuleDetails = root.lookup("ModuleDetails");
                this.NativeClassInfo = root.lookup("NativeClassInfo");
                this.GetNativeClassTableOutput = root.lookup("GetNativeClassTableOutput");
                this.PluginPackInfo = root.lookup("PluginPackInfo");
                this.GetPluginThirdPartyNoticesOutput = root.lookup("GetPluginThirdPartyNoticesOutput");
                this.CameraInfo = root.lookup("CameraInfo");
                this.GetPreviewJpegInput = root.lookup("GetPreviewJpegInput");
                this.GetPreviewJpegOutput = root.lookup("GetPreviewJpegOutput");
                this.ProcessorClassInfo = root.lookup("ProcessorClassInfo");
                this.GetProcessorClassTableOutput = root.lookup("GetProcessorClassTableOutput");
                this.SessionFilterFlags = root.lookup("SessionFilterFlags");
                this.GetSessionFilterTableOutput = root.lookup("GetSessionFilterTableOutput");
                this.StringAndBool = root.lookup("StringAndBool");
                this.SignalTreeNode = root.lookup("SignalTreeNode");
                this.GetSignalTreeOutput = root.lookup("GetSignalTreeOutput");
                this.TaskClassInfo = root.lookup("TaskClassInfo");
                this.GetTaskClassTableOutput = root.lookup("GetTaskClassTableOutput");
                this.IntSize = root.lookup("IntSize");
                this.VideoInputMode = root.lookup("VideoInputMode");
                this.VideoChannelInfo = root.lookup("VideoChannelInfo");
                this.GetVideoChannelsInfoOutput = root.lookup("GetVideoChannelsInfoOutput");
                this.VideoDeviceID = root.lookup("VideoDeviceID");
                this.VideoOutputMode = root.lookup("VideoOutputMode");
                this.VideoDeviceInfo = root.lookup("VideoDeviceInfo");
                this.GetVideoDevicesOutput = root.lookup("GetVideoDevicesOutput");
                this.IntRect = root.lookup("IntRect");
                this.GetVideoFrameImageInput = root.lookup("GetVideoFrameImageInput");
                this.CommonImage = root.lookup("CommonImage");
                this.GetVideoFrameImageOutput = root.lookup("GetVideoFrameImageOutput");
                this.GetVideoFrameThumbnailInput = root.lookup("GetVideoFrameThumbnailInput");
                this.IntAndDouble = root.lookup("IntAndDouble");
                this.BusMessageSample = root.lookup("BusMessageSample");
                this.BusSignalValue = root.lookup("BusSignalValue");
                this.ParseBusMessageOutput = root.lookup("ParseBusMessageOutput");
                this.PublishDataInput = root.lookup("PublishDataInput");
                this.BusMessage = root.lookup("BusMessage");
                this.SendRawDataInput = root.lookup("SendRawDataInput");
                this.IntAndString = root.lookup("IntAndString");
                this.StringAndDouble = root.lookup("StringAndDouble");
                this.SetSessionPropertiesInput = root.lookup("SetSessionPropertiesInput");
                this.StartOfflineOnlineInput = root.lookup("StartOfflineOnlineInput");
                this.StartRemoteInput = root.lookup("StartRemoteInput");
                this.StartRemoteWithControllerInput = root.lookup("StartRemoteWithControllerInput");
                this.StartReplayInput = root.lookup("StartReplayInput");
                this.TwoBools = root.lookup("TwoBools");
                this.SubscribeDataInput = root.lookup("SubscribeDataInput");
                this.SwitchAppModeInput = root.lookup("SwitchAppModeInput");
                this.OptionalULong = root.lookup("OptionalULong");
                this.GPUDecoderTestResults = root.lookup("GPUDecoderTestResults");
                this.ThreeStrings = root.lookup("ThreeStrings");
                this.CallNativeFunctionInput = root.lookup("CallNativeFunctionInput");
                this.DataReferenceInfo = root.lookup("DataReferenceInfo");
                this.GenerateBusMessageInput = root.lookup("GenerateBusMessageInput");
                resolve();
            });
        });
    };

    this.defaultGetCallAgency = (func, id, genInput) => {

        if (id == null) id = "";
        else if (typeof id != "string") id = id.toString();
        if (genInput == null) genInput = this.agencyDummyGenInput;

        let context = this.agencyGetOrCreateCtx(func, id, this.agencyDefaultGetContextCreator);

        let shouldSendInput = !context.sentInput;
        context.sentInput = true;
        if (shouldSendInput && !this.agencyTryEnqueue(func, id, genInput(), false)) {
            if (context.shouldRemove(true)) this.agencyTryRemove(func, id);
            return null;
        }

        if (context.gotOutput) {
            if (context.shouldRemove(false)) this.agencyTryRemove(func, id);
            return new Promise((resolve) => {
                resolve(context.output);
            });
        }

        let t0 = Date.now();
        return new Promise((resolve) => {
            let timerID = setInterval(() => {
                let shouldContinue = false;
                if (context.removed) {
                    resolve(context.gotOutput ? context.output : null);
                }
                else if (Date.now() - t0 > this.AgencyOutputWaitTimeout || !this.isConnected()) {
                    if (context.shouldRemove(true)) this.agencyTryRemove(func, id);
                    resolve(null);
                }
                else if (context.gotOutput) {
                    if (context.shouldRemove(false)) this.agencyTryRemove(func, id);
                    resolve(context.output);
                }
                else shouldContinue = true;
                if (!shouldContinue) {
                    clearInterval(timerID);
                }
            }, 10);
        });
    };

    this.counterRunCallAgency = (func, genInput, optional, intervalControl) => {

        if (genInput == null) genInput = this.agencyDummyGenInput;

        let contextResult = this.agencyGetOrCreateCounterRunCtx(func, intervalControl);
        let newContext = contextResult[0];
        let context = contextResult[1];
        let id = context.id;

        if (newContext && !this.agencyTryEnqueue(func, id, genInput(), optional)) {
            this.agencyTryRemove(func, id);
            context.removed = true;
            return null;
        }

        if (context.gotOutput) {
            if (newContext) {
                this.agencyTryRemove(func, id);
                context.removed = true;
            }
            return new Promise((resolve) => {
                resolve(context.output);
            });
        }

        let t0 = Date.now();
        return new Promise((resolve) => {
            let timerID = setInterval(() => {
                let shouldContinue = false;
                if (!newContext && context.removed) {
                    resolve(context.gotOutput ? context.output : null);
                }
                else if (Date.now() - t0 > this.AgencyOutputWaitTimeout || !this.isConnected()) {
                    if (newContext) {
                        this.agencyTryRemove(func, id);
                        context.removed = true;
                    }
                    resolve(null);
                }
                else if (context.gotOutput) {
                    if (newContext) {
                        this.agencyTryRemove(func, id);
                        context.removed = true;
                    }
                    resolve(context.output);
                }
                else shouldContinue = true;
                if (!shouldContinue) {
                    clearInterval(timerID);
                }
            }, 10);
        });
    };

    this.handleDefaultSetNode = (context, node, func, id, optional) => {

        let genInputForSending = null;
        if (context.queue.length > 0 && node == context.queue[0] && !node.sentInput) {
            genInputForSending = node.genInput;
            node.sentInput = true;
        }
        if (genInputForSending != null && !this.agencyTryEnqueue(func, id, genInputForSending(), optional)) {
            if (context.shouldRemove(true)) this.agencyTryRemove(func, id);
            return [ true, null ];
        }

        if (node.gotOutput) {
            if (node == context.queue[0]) context.queue.shift();
            else if (node == context.queue[1]) context.queue.pop();
            if (context.shouldRemove(false)) this.agencyTryRemove(func, id);
            return [ true, node.output ];
        }

        return [ false, null ];
    };

    this.defaultSetCallAgency = (func, id, genInput, optional, infinite) => {

        if (optional) infinite = false;
        if (id == null) id = "";
        else if (typeof id != "string") id = id.toString();
        if (genInput == null) genInput = this.agencyDummyGenInput;

        let context = this.agencyGetOrCreateCtx(func, id, this.agencyDefaultSetContextCreator);

        let node = null;
        if (context.queue.length == 2) node = context.queue[1];
        else if (context.queue.length == 1 && !context.queue[0].sentInput) node = context.queue[0];
        else {
            node = new AppoxI7DefaultSetNode();
            context.queue.push(node);
        }
        node.genInput = genInput;

        let firstHandleResult = this.handleDefaultSetNode(context, node, func, id, optional);
        if (firstHandleResult[0]) {
            return new Promise((resolve) => {
                resolve(firstHandleResult[1]);
            });
        }

        let t0 = Date.now();
        return new Promise((resolve) => {
            let timerID = setInterval(() => {
                let shouldContinue = false;
                if (context.removed) {
                    resolve(context.gotOutput ? context.output : null);
                }
                else if ((!infinite && Date.now() - t0 > this.AgencyOutputWaitTimeout) || !this.isConnected()) {
                    if (context.shouldRemove(true)) this.agencyTryRemove(func, id);
                    resolve(null);
                }
                else {
                    let handleResult = this.handleDefaultSetNode(context, node, func, id, optional);
                    if (handleResult[0]) resolve(handleResult[1]);
                    else shouldContinue = true;
                }
                if (!shouldContinue) {
                    clearInterval(timerID);
                }
            }, 10);
        });
    };

    this.exclusiveRunCallAgency = (func, genInput) => {

        if (genInput == null) genInput = this.agencyDummyGenInput;

        let id = "";
        let context = this.agencyGetOrCreateCtx(func, id, this.agencyExclusiveRunContextCreator);

        if (context.occupied) {
            return new Promise((resolve) => {
                resolve(null);
            });
        }
        else context.occupied = true;

        if (!this.agencyTryEnqueue(func, id, genInput(), false)) {
            this.agencyTryRemove(func, id);
            return new Promise((resolve) => {
                resolve(null);
            });
        }

        if (context.gotOutput) {
            this.agencyTryRemove(func, id);
            return new Promise((resolve) => {
                resolve(context.output);
            });
        }

        let t0 = Date.now();
        return new Promise((resolve) => {
            let timerID = setInterval(() => {
                let shouldContinue = false;
                if (!this.isConnected()) {
                    this.agencyTryRemove(func, id);
                    resolve(null);
                }
                else if (context.gotOutput) {
                    this.agencyTryRemove(func, id);
                    resolve(context.output);
                }
                else shouldContinue = true;
                if (!shouldContinue) {
                    clearInterval(timerID);
                }
            }, 10);
        });
    };

    this.agencyDummyGenInput = () => {
        return null;
    };

    this.agencyDefaultGetContextCreator = () => {
        return new AppoxI7DefaultGetContext();
    };

    this.agencyDefaultSetContextCreator = () => {
        return new AppoxI7DefaultSetContext();
    };

    this.agencyExclusiveRunContextCreator = () => {
        return new AppoxI7ExclusiveRunContext();
    };

    this.agencyGetOrCreateCtx = (func, id, creator) => {
        if (!this.agencyCallCtx.has(func)) this.agencyCallCtx.set(func, new Map());
        let table = this.agencyCallCtx.get(func);
        if (!table.has(id)) table.set(id, creator());
        return table.get(id);
    };

    this.agencyGetOrCreateCounterRunCtx = (func, intervalControl) => {
        
        if (!this.agencyCallCtx.has(func)) this.agencyCallCtx.set(func, new Map());

        if (!intervalControl) {
            let ctx = new AppoxI7CounterRunContext(++this.cnterRunAtom);
            this.agencyCallCtx.get(func).set(ctx.id, ctx);
            return [ true, ctx ];
        }

        if (!this.agencyCounterRunLastInvoke.has(func)) this.agencyCounterRunLastInvoke.set(func, new AppoxI7CounterRunLastInvoke());
        let invoke = this.agencyCounterRunLastInvoke.get(func);

        let now = Date.now();
        if (invoke.time != null) {
            if (now < invoke.time) invoke.time = now;
            else if (now - invoke.time < this.CnterRunInterval) return [ false, invoke.context ];
        }

        let newCtx = new AppoxI7CounterRunContext(++this.cnterRunAtom);
        this.agencyCallCtx.get(func).set(newCtx.id, newCtx);
        invoke.context = newCtx;
        invoke.time = now;
        return [ true, newCtx ];
    };

    this.agencyTryEnqueue = (func, id, data, optional) => {
        if (!this.isConnected()) return false;
        let packet = this.generateAgencyInputPacket(func, id, data);
        if (optional) this.agencyInputOptionalPacketsSrc.push(new AppoxI7AgencyInputPacket(packet));
        else this.agencyInputRequiredPacketsSrc.push(new AppoxI7AgencyInputPacket(packet));
        return true;
    };

    this.agencyTryRemove = (func, id) => {
        if (!this.agencyCallCtx.has(func)) return;
        this.agencyCallCtx.get(func).delete(id);
    };

    this.processAgencyInputQueue = (src, target) => {

        if (src.length == 0) return;

        let aiTotalSize = 0;
        let aiBuffer = [];
        
        for (let i = 0; i < src.length; i++) {
            let data = src[i].packet;
            if (aiTotalSize + data.length <= this.MaxAgencyInputPackSize) {
                aiTotalSize += data.length;
                aiBuffer.push(data);
                continue;
            }

            if (aiBuffer.length > 1) {
                target.push(this.generateAgencyInputsPacket(aiTotalSize, aiBuffer));
            }
            else if (aiBuffer.length == 1) {
                target.push(new AppoxI7AgencyInputPacket(aiBuffer[0]));
            }
            aiTotalSize = 0;
            aiBuffer = [];

            if (data.length >= this.MaxAgencyInputPackSize) {

                let protocol = this.Protocol.AgencyInput;

                let [isZipped, zippedData] = this.tryZip(data);
                if (isZipped) {
                    data = zippedData;
                    protocol = this.Protocol.Zipped;
                }

                let packets = this.generateSegmentPackets(data, protocol);
                packets.forEach((p) => {
                    target.push(new AppoxI7AgencyInputPacket(p));
                });
            }
            else {
                aiTotalSize += data.length;
                aiBuffer.push(data);
            }
        }

        if (aiBuffer.length > 1) {
            target.push(this.generateAgencyInputsPacket(aiTotalSize, aiBuffer));
        }
        else if (aiBuffer.length == 1) {
            target.push(new AppoxI7AgencyInputPacket(aiBuffer[0]));
        }

        src.length = 0;
    };

    this.generateAgencyInputsPacket = (totalSize, buffer) => {

        let actualSize = 4 + totalSize - 6 * buffer.length;
        let packet = new Uint8Array(actualSize);
        let uint32View = new Uint32Array(packet.buffer, 0, 1);
        uint32View[0] = this.Protocol.AgencyInputs;
        
        let currentIndex = 4;
        for (let i = 0; i < buffer.length; i++) {

            let aiPacketData = buffer[i];

            let jsonLen = new DataView(aiPacketData.buffer, aiPacketData.byteOffset + 4, 4).getUint32(0, true);
            if (jsonLen >= 65536) continue; // 只可能是ID过长导致，需要修正该函数的ID机制

            let jsonLenView = new DataView(packet.buffer, currentIndex, 2);
            jsonLenView.setUint16(0, jsonLen, true);
            currentIndex += 2;

            packet.set(new Uint8Array(aiPacketData.buffer, aiPacketData.byteOffset + 8, aiPacketData.length - 8), currentIndex);
            currentIndex += aiPacketData.length - 8;
        }
        if (currentIndex < packet.length) {
            packet = packet.slice(0, currentIndex);
        }

        packet = this.tryZip(packet)[1];
        return new AppoxI7AgencyInputPacket(packet);
    };

    this.tryZip = (packet) => {
        if (packet.length >= this.MinZipSize && packet.length <= this.MaxZipSize) {
            let compressedData = pako.gzip(packet);
            if (compressedData.length <= (1.0 - this.MinZipRatio) * packet.length) {
                let result = new Uint8Array(4 + compressedData.length);
                let dataView = new DataView(result.buffer);
                dataView.setUint32(0, this.Protocol.Zipped, true);
                result.set(compressedData, 4);
                return [true, result];
            }
        }
        return [false, packet];
    };

    this.bytesToString = (bytes, defaultVal) => {
        if (bytes == null || bytes.length < 1 || bytes.length > this.MaxStringLength) return defaultVal;
        if (bytes[0] == 0) return defaultVal;
        return this.textDecoder.decode(new Uint8Array(bytes.buffer, bytes.byteOffset + 1, bytes.length - 1));
    };

    this.stringToBytes = (str) => {
        if (str == null || str.length > this.MaxStringLength) return [0];
        else {
            let utf8Bytes = this.textEncoder.encode(str);
            let bytes = new Uint8Array(1 + utf8Bytes.length);
            bytes[0] = 1;
            bytes.set(utf8Bytes, 1);
            return bytes;
        }
    };

    this.bytesToSession = (bytes) => {
        if (bytes == null) return null;
        let sessionIdString = this.textDecoder.decode(bytes);
        if (sessionIdString.length != 19) return null;
        let session = new AppoxSessionIdentifier(sessionIdString);
        return session.isValid ? session : null;
    };

    this.sessionToBytes = (session) => {
        if (session == null || !(session instanceof AppoxSessionIdentifier)) return null;
        return this.textEncoder.encode(session.toString());
    };

    this.bytesToEnum = (enumClass, bytes, defaultVal) => {
        if (bytes == null || bytes.length == 0) return defaultVal;
        let enumString = this.textDecoder.decode(bytes);
        return this.stringToEnum(enumClass, enumString, defaultVal);
    };

    this.enumToBytes = (enumValue) => {
        return this.textEncoder.encode(enumValue);
    };

    this.stringToEnum = (enumClass, enumString, defaultVal) => {
        let enumValue = enumClass[enumString];
        return enumValue == null ? defaultVal : enumValue;
    }

    this.bytesToDouble = (bytes, defaultVal) => {
        if (bytes == null || bytes.length != 8) return defaultVal;
        let dataView = new DataView(bytes.buffer, bytes.byteOffset, 8);
        return dataView.getFloat64(0, true);
    };

    this.doubleToBytes = (num) => {
        let bytes = new Uint8Array(8);
        let dataView = new DataView(bytes.buffer);
        dataView.setFloat64(0, num, true);
        return bytes;
    };

    this.bytesToInt = (bytes, defaultVal) => {
        if (bytes == null || bytes.length != 4) return defaultVal;
        let dataView = new DataView(bytes.buffer, bytes.byteOffset, 4);
        return dataView.getInt32(0, true);
    };

    this.intToBytes = (num) => {
        let bytes = new Uint8Array(4);
        let dataView = new DataView(bytes.buffer);
        dataView.setInt32(0, num, true);
        return bytes;
    };

    this.bytesToBool = (bytes) => {
        if (bytes == null || bytes.length != 1) return false;
        return bytes[0] == 1;
    };

    this.boolToBytes = (bool) => {
        let bytes = new Uint8Array(1);
        bytes[0] = bool ? 1 : 0;
        return bytes;
    };

    this.bytesToFloat = (bytes, defaultVal) => {
        if (bytes == null || bytes.length != 4) return defaultVal;
        let dataView = new DataView(bytes.buffer, bytes.byteOffset, 4);
        return dataView.getFloat32(0, true);
    };

    this.floatToBytes = (num) => {
        let bytes = new Uint8Array(4);
        let dataView = new DataView(bytes.buffer);
        dataView.setFloat32(0, num, true);
        return bytes;
    };

    this.bytesToBigInt = (bytes, defaultVal) => {
        if (defaultVal != null && typeof defaultVal != "bigint") defaultVal = BigInt(defaultVal);
        if (bytes == null || bytes.length != 8) return defaultVal;
        let dataView = new DataView(bytes.buffer, bytes.byteOffset, 8);
        return dataView.getBigUint64(0, true);
    };

    this.bigIntToBytes = (num) => {
        if (num == null) num = BigInt(0);
        else if (typeof num != "bigint") num = BigInt(num);
        let bytes = new Uint8Array(8);
        let dataView = new DataView(bytes.buffer);
        dataView.setBigUint64(0, num, true);
        return bytes;
    };

    this.longToBigInt = (num, defaultVal) => {
        if (defaultVal != null && typeof defaultVal != "bigint") defaultVal = BigInt(defaultVal);
        if (num == null) return defaultVal;
        if (Long.isLong(num)) return BigInt(num.toString());
        if (typeof num == "number") return BigInt(num);
        else return defaultVal;
    };

    this.bigIntToLong = (num, signed) => {
        if (num == null) num = BigInt(0);
        else if (typeof num != "bigint") num = BigInt(num);
        return Long.fromString(num.toString(), !signed);
    };

    this.stringOr8CharsHashToLong = (str, signed) => {
        if (str.length == 8) {
            let low = str.charCodeAt(0) + 256 * (str.charCodeAt(1) + 256 * (str.charCodeAt(2) + 256 * str.charCodeAt(3)));
            let high = str.charCodeAt(4) + 256 * (str.charCodeAt(5) + 256 * (str.charCodeAt(6) + 256 * str.charCodeAt(7)));
            return Long.fromBits(low, high, !signed);
        }
        else {
            return Long.fromString(str, !signed);
        }
    };

    this.stringToDate = (str) => {
        if (str == null || typeof str != "string" || str.length != 23) return null;
        let year = Number(str.slice(0, 4));
        let month = Number(str.slice(5, 7));
        let day = Number(str.slice(8, 10));
        let hour = Number(str.slice(11, 13));
        let minute = Number(str.slice(14, 16));
        let second = Number(str.slice(17, 19));
        let ms = Number(str.slice(20, 23));
        return new Date(year, month - 1, day, hour, minute, second, ms);
    };

    this.dateToString = (date) => {
        if (date == null || !(date instanceof Date)) return null;
        let year = date.getFullYear();
        let month = date.getMonth() + 1;
        let day = date.getDate();
        let hour = date.getHours();
        let minute = date.getMinutes();
        let second = date.getSeconds();
        let ms = date.getMilliseconds();
        return String(year).padStart(4, '0') + "-" + String(month).padStart(2, '0') + "-" + String(day).padStart(2, '0') + "-" + String(hour).padStart(2, '0') + "-" + String(minute).padStart(2, '0') + "-" + String(second).padStart(2, '0') + "-" + String(ms).padStart(3, '0');
    };

    this.convToAppoxTimestamp = (srcT) => {
        if (srcT == null) return null;
        let tos = appoxEnums.TimeOffsetSync;
        let dstSync = this.stringToEnum(tos, srcT.offsetSync, null);
        let dstSession = new AppoxSessionIdentifier(srcT.session);
        if (dstSync != null && dstSession.isValid) {
            let srcInfo = srcT.timeInfo;
            let dstInfo = null;
            if (srcInfo != null) {
                let dstCpuTick = this.longToBigInt(srcInfo.cpuTick, 0);
                let dstHostPosix = this.longToBigInt(srcInfo.hostPosix, 0);
                let dstGuestPosix = this.longToBigInt(srcInfo.guestPosix, 0);
                let dstGnssPosix = this.longToBigInt(srcInfo.gnssPosix, 0);
                dstInfo = new AppoxIndependentTimeInfo(dstCpuTick, dstHostPosix, dstGuestPosix, dstGnssPosix);
            }
            return new AppoxTimestamp(dstSession, srcT.offset, dstSync, dstInfo);
        }
        return null;
    };

    this.convToAppoxSignalTreeNode = (srcNode) => {
        if (srcNode == null) return null;
        let stnt = appoxEnums.SignalTreeNodeType;
        let dstType = this.stringToEnum(stnt, srcNode.type, null);
        if (dstType == null) return null;
        let dstChildren = [];
        srcNode.children.forEach((srcChildNode) => {
            let dstChildNode = this.convToAppoxSignalTreeNode(srcChildNode);
            if (dstChildNode != null) dstChildren.push(dstChildNode);
        });
        if (dstChildren.length == 0) dstChildren = null;
        return new AppoxSignalTreeNode(dstType, srcNode.id, srcNode.name, dstChildren);
    };

    this.convToAppoxVideoInputMode = (srcMode) => {
        if (srcMode == null) return null;
        let vdc = appoxEnums.VideoDataCodec;
        let srcSize = srcMode.size;
        let dstCodec = this.stringToEnum(vdc, srcMode.inputCodec, vdc.Invalid);
        let dstSize = new AppoxIntSize(srcSize.width, srcSize.height);
        return new AppoxVideoInputMode(dstCodec, dstSize);
    };

    this.convToAppoxCommonImage = (srcImage) => {
        if (srcImage == null) return null;
        return new AppoxCommonImage(srcImage.width, srcImage.height, srcImage.withAlpha, srcImage.bgrInverted, srcImage.rowBytes, srcImage.data);
    };

    this.convToAppoxCameraInfo = (srcInfo) => {
        if (srcInfo == null) return null;
        let sct = appoxEnums.SpecialCameraType;
        return new AppoxCameraInfo(this.stringToEnum(sct, srcInfo.specialCameraType, sct.Unknown), srcInfo.metaProtocol, srcInfo.meta, srcInfo.horizontalFov);
    };

    this.convNumberToTimeOffsetSync = (num) => {
        switch (num) {
            case 0:
                return appoxEnums.TimeOffsetSync.HostArrival;
            case 1:
                return appoxEnums.TimeOffsetSync.Server;
            case 2:
                return appoxEnums.TimeOffsetSync.Gnss;
            case 3:
                return appoxEnums.TimeOffsetSync.BusReceiverArrival;
            case 4:
                return appoxEnums.TimeOffsetSync.Interpolated;
            default:
                return appoxEnums.TimeOffsetSync.HostArrival;
        }
    };

    this.convNumberToBusChannelType = (num) => {
        switch (num) {
            case 0:
                return appoxEnums.BusChannelType.None;
            case 1:
                return appoxEnums.BusChannelType.Can;
            case 2:
                return appoxEnums.BusChannelType.CanFD;
            case 3:
                return appoxEnums.BusChannelType.Lin;
            case 4:
                return appoxEnums.BusChannelType.Flexray;
            case 5:
                return appoxEnums.BusChannelType.Ethernet;
            case 6:
                return appoxEnums.BusChannelType.SomeIP;
            default:
                return appoxEnums.BusChannelType.None;
        }
    };

    this.convNumberToBusRawDataState = (num) => {
        switch (num) {
            case 0:
                return appoxEnums.BusRawDataState.Received;
            case 1:
                return appoxEnums.BusRawDataState.NotRunning;
            case 2:
                return appoxEnums.BusRawDataState.InvalidChannel;
            case 3:
                return appoxEnums.BusRawDataState.PluginNotFound;
            case 4:
                return appoxEnums.BusRawDataState.NotSync;
            case 5:
                return appoxEnums.BusRawDataState.ScheduleUnsupported;
            case 6:
                return appoxEnums.BusRawDataState.TimeDisorder;
            case 7:
                return appoxEnums.BusRawDataState.TransmitOK;
            case 8:
                return appoxEnums.BusRawDataState.TransmitFailed;
            case 9:
                return appoxEnums.BusRawDataState.ScheduleOK;
            case 10:
                return appoxEnums.BusRawDataState.ScheduleFailed;
            default:
                return appoxEnums.BusRawDataState.Received;
        }
    };

    this.generateUuidV4 = () => {
        return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
            (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
        );
    };
}

function AppoxI7RemoteSegmentRestore() {

    this.protocolMap = {};
    for (let key in appoxi7.Protocol) {
        this.protocolMap[appoxi7.Protocol[key].toString()] = appoxi7.Protocol[key];
    }

    this.tryUnzip = (packet) => {
        let uint32View = new Uint32Array(packet.buffer, packet.byteOffset, 1);
        if (uint32View[0] != appoxi7.Protocol.Zipped) return packet;
        let compressedData = new Uint8Array(packet.buffer, packet.byteOffset + 4, packet.length - 4);
        let decompressedData = pako.inflate(compressedData);
        return new Uint8Array(decompressedData);
    };

    this.restore = (receivedPackets) => {

        let restoredPackets = [];
        receivedPackets.forEach((packet) => {

            if (packet.length < 8) {
                restoredPackets.push(packet);
                return;
            }

            let uint32View = new Uint32Array(packet.buffer, packet.byteOffset, 2);
            let protocol = uint32View[0];
            let jsonSize = uint32View[1];
            if (protocol != appoxi7.Protocol.Segment) {
                restoredPackets.push(this.tryUnzip(packet));
                return;
            }

            let input = appoxi7.parseJson(packet, 8, jsonSize);
            let inputGUID = input.I ?? input.GUID;
            let inputProtocol = input.P ?? input.Protocol;
            let inputTotalSize = input.S ?? input.TotalSize;
            let inputTotalSegments = input.N ?? input.TotalSegments;
            let inputSegmentIndex = input.X ?? input.SegmentIndex;
            let inputSegmentSize = input.Z ?? input.SegmentSize;

            if (packet.length != 8 + jsonSize + inputSegmentSize) return;

            if (this.ctxs[inputGUID] == null) {
                if (inputSegmentIndex != 0) return;
                this.ctxs[inputGUID] = new AppoxI7SegmentRestoreContext(inputTotalSize, inputProtocol);
            }
            let ctx = this.ctxs[inputGUID];

            if (inputSegmentIndex != ctx.nextIndex || inputProtocol != ctx.protocolString || ctx.writtenBytes + inputSegmentSize > ctx.buffer.length) {
                delete this.ctxs[inputGUID];
                return;
            }

            ctx.buffer.set(new Uint8Array(packet.buffer, packet.byteOffset + 8 + jsonSize, inputSegmentSize), ctx.writtenBytes);
            ctx.writtenBytes += inputSegmentSize;
            ctx.nextIndex++;
            ctx.lastReceivedTime = Date.now();

            if (ctx.nextIndex == inputTotalSegments) {
                if (ctx.writtenBytes == ctx.buffer.length && ctx.buffer.length >= 4) {
                    uint32View = new Uint32Array(ctx.buffer.buffer, ctx.buffer.byteOffset, 1);
                    let protocol = this.protocolMap[ctx.protocolString];
                    if (protocol != null && uint32View[0] == protocol) {
                        restoredPackets.push(this.tryUnzip(ctx.buffer));
                    }
                }
                delete this.ctxs[inputGUID];
            }
        });

        let now = Date.now();
        for (let key in this.ctxs) {
            let ctx = this.ctxs[key];
            if (now - ctx.lastReceiveTime > appoxi7.SegmentRestoreTimeout) delete this.ctxs[key];
        }

        return restoredPackets;
    };

    this.ctxs = {};
}
export { appoxi7 };

function AppoxI7SegmentRestoreContext(bufferSize, protocolString) {
    this.buffer = new Uint8Array(bufferSize);
    this.writtenBytes = 0;
    this.nextIndex = 0;
    this.protocolString = protocolString;
    this.lastReceivedTime = 0;
}

function AppoxI7AgencyInputPacket(packet) {
    this.packet = packet;
    this.time = Date.now();
}

function AppoxI7DefaultGetContext() {

    this.sendInput = false;
    this.output = null;
    this.gotOutput = false;
    this.removed = false;

    this.shouldRemove = (failed) => {
        if (this.removed) return false;
        this.removed = failed || this.gotOutput;
        return this.removed;
    };

    this.setOutput = (output) => {
        this.output = output;
        this.gotOutput = true;
    };
}

function AppoxI7CounterRunContext(idNumber) {

    this.id = idNumber.toString();
    this.output = null;
    this.gotOutput = false;
    this.removed = false;

    this.setOutput = (output) => {
        this.output = output;
        this.gotOutput = true;
    };
}

function AppoxI7CounterRunLastInvoke() {
    this.context = null;
    this.time = null;
}

function AppoxI7DefaultSetNode() {
    this.sentInput = false;
    this.genInput = null;
    this.output = null;
    this.gotOutput = false;
}

function AppoxI7DefaultSetContext() {

    this.removed = false;
    this.queue = [];
    
    this.shouldRemove = (failed) => {
        if (this.removed) return false;
        this.removed = failed || this.queue.length == 0 || this.queue[this.queue.length - 1].gotOutput;
        return this.removed;
    };

    this.setOutput = (output) => {
        if (this.queue.length == 0) return;
        let target = this.queue[0];
        if (target.gotOutput) target = this.queue[this.queue.length - 1];
        target.output = output;
        target.gotOutput = true;
    };
}

function AppoxI7ExclusiveRunContext() {

    this.occupied = false;
    this.output = null;
    this.gotOutput = false;
    
    this.setOutput = (output) => {
        this.output = output;
        this.gotOutput = true;
    };
}

function AppoxI7RunTaskCallbackImpl(guid, config) {

    AppoxRunTaskCallback.call(this);
    this.guid = guid;
    this.config = config;
    this.state = appoxEnums.TaskState.Unknown;
    this.progress = 0;
    this.description = null;
    this.returnValue = null;
    this.canceled = false;

    this.getTaskState = () => {
        return this.state;
    };

    this.getTaskStateDescription = () => {
        return this.description || "";
    };

    this.getTaskProgress = () => {
        return this.progress;
    };

    this.getTaskConfig = () => {
        return this.config;
    };

    this.getTaskReturnValue = () => {
        return this.returnValue || "";
    };

    this.cancelTask = () => {
        return new Promise((resolve) => {

            if (!appoxi7.isConnected()) {
                resolve(false);
                return;
            }

            let packet = appoxi7.generateTaskCancelPacket(this.guid);
            let rawPacket = appoxi7.generateRawPacket(packet);
            appoxi7.socket.send(rawPacket);

            let checkInterval = setInterval(() => {
                if (!appoxi7.isConnected()) {
                    clearInterval(checkInterval);
                    resolve(false);
                }
                if (this.state == appoxEnums.TaskState.Finished || 
                    this.state == appoxEnums.TaskState.Failed) {
                    clearInterval(checkInterval);
                    resolve(this.canceled);
                }
            }, 10);
        });
    };

    this.onTaskProgress = (progress, description) => {
        if (this.state == appoxEnums.TaskState.Unknown || this.state == appoxEnums.TaskState.Progressing) {
            this.state = appoxEnums.TaskState.Progressing;
            this.progress = progress;
            this.description = description;
        }
    };

    this.onTaskResult = (succeeded, canceled, description, returnValue) => {
        if (succeeded) this.progress = 100;
        this.returnValue = returnValue;
        this.description = description;
        this.canceled = canceled;
        this.state = succeeded ? appoxEnums.TaskState.Finished : appoxEnums.TaskState.Failed;
    };

    this.onDisconnected = () => {
        this.description = appoxCallback.onGetAppLanguage() == appoxEnums.Language.Chinese ? "与主机的连接已断开。" : "Host disconnected.";
        this.state = appoxEnums.TaskState.Failed;
    };
}

function AppoxI7TaskManager() {

    this.callbackTable = {};

    this.register = (guid, callback) => {
        this.callbackTable[guid] = callback;
    };

    this.unregister = (guid) => {
        delete this.callbackTable[guid];
    };

    this.onDisconnected = () => {
        for (let guid in this.callbackTable) {
            this.callbackTable[guid].onDisconnected();
        }
    };

    this.onTaskProgress = (guid, progress, description) => {
        let callback = this.callbackTable[guid];
        if (callback) {
            callback.onTaskProgress(progress, description);
        }
    };

    this.onTaskResult = (guid, succeeded, canceled, description, returnValue) => {
        let callback = this.callbackTable[guid];
        if (callback) {
            callback.onTaskResult(succeeded, canceled, description, returnValue);
        }
    };
}

function AppoxI7SessionSamplesContext() {

    this.sessionTimeline = null;
    this.sessionSamples = [];

    this.addSample = async (sample) => {
        this.sessionSamples.push(sample);
        this.ensureSessionTimeline(sample.timestamp.session);
    }

    this.dequeueSamples = () => {
        if (this.sessionTimeline == null) return [];
        for (let sample of this.sessionSamples) {
            sample.timeline = this.sessionTimeline + sample.timestamp.offset;
        }
        let samples = this.sessionSamples;
        this.sessionSamples = [];
        return samples;
    }

    this.ensureSessionTimeline = async (session) => {
        if (this.sessionTimeline == null) {
            let result = await agencyAsync.getSessionTimeline(session);
            if (result != null && this.sessionTimeline == null) {
                this.sessionTimeline = result;
            }
        }
    }
}

function AppoxI7HostStatusMonitor() {

    this.appMode = appoxEnums.ApplicationMode.Unknown;
    this.dataPath = null;
    this.dataLayer = null;
    this.resetDataFlag = false;
    this.checkingResetData = false;

    this.getAppMode = () => {
        return this.appMode;
    }

    this.shouldResetData = () => {
        this.checkResetData();
        let result = this.resetDataFlag;
        this.resetDataFlag = false;
        return result;
    }

    this.checkResetData = async () => {
        if (this.resetDataFlag || this.checkingResetData) return;
        this.checkingResetData = true;
        let result = await Promise.all([
            agencyAsync.getAppMode(),
            agencyAsync.getGlobalParameter("System.DataPath", null),
            agencyAsync.getCurrentDataLayer()
        ]);
        this.resetDataFlag = this.resetDataFlag || this.appMode != result[0] || this.dataPath != result[1] || this.dataLayer != result[2];
        this.appMode = result[0];
        this.dataPath = result[1];
        this.dataLayer = result[2];
        this.checkingResetData = false;
    }
}

function AppoxI7PointCloudSlice(channel, session, offset, sourcePointCount, sync, timeInfo, withIntensities, data) {
    this.channel = channel;
    this.session = session;
    this.offset = offset;
    this.sourcePointCount = sourcePointCount;
    this.sync = sync;
    this.timeInfo = timeInfo;
    this.withIntensities = withIntensities;
    this.data = data;
}

function AppoxI7PointCloudRestoreChannel() {
    this.session = new AppoxSessionIdentifier(null);
    this.offset = 0;
    this.sourcePointCount = 0;
    this.sync = appoxEnums.TimeOffsetSync.HostArrival;
    this.timeInfo = null;
    this.withIntensities = false;
    this.data = [];
}

function AppoxI7PointCloudRestore() {
    
    this.channels = new Array(12);
    this.outputStream = [];

    this.reset = () => {
        for (let i = 0; i < this.channels.length; i++) {
            this.channels[i] = new AppoxI7PointCloudRestoreChannel();
        }
        this.outputStream = [];
    }

    this.reset();

    this.updateWithReferences = (references) => {
        for (let i = 0; i < references.length; i++) {
            if (references[i] <= 0 && this.channels[i].session.isValid) {
                this.channels[i] = new AppoxI7PointCloudRestoreChannel();
            }
        }
    }
    
    this.updateWithSlice = (slice, references) => {
        if (slice.channel < 0 || slice.channel >= this.channels.length || references[slice.channel] <= 0) return;
        
        let ctx = this.channels[slice.channel];
        if (slice.session.equals(ctx.session) && slice.offset === ctx.offset) {
            ctx.data.push(slice.data);
        } else {
            if (ctx.data.length > 0) {
                let dataByteCount = 0;
                ctx.data.forEach(data => dataByteCount += data.length);
                let step = ctx.withIntensities ? 5 : 4;
                let pointCount = dataByteCount / step;

                let points = new Int16Array(pointCount * 3);
                let intensities = ctx.withIntensities ? new Uint8Array(pointCount) : null;

                let index = 0;
                ctx.data.forEach(data => {
                    let curCount = data.length / step;
                    for (let i = 0; i < curCount; i++) {
                        let b0 = data[step * i];
                        let b1 = data[step * i + 1];
                        let b2 = data[step * i + 2];
                        let xCode = b0 | (((b1 & 0x0f) << 8));
                        let yCode = ((b1 & 0xf0) >> 4) | (b2 << 4);
                        let zCode = data[step * i + 3];
                        
                        points[index * 3] = ((xCode - 2048) * 5);
                        points[index * 3 + 1] = ((yCode - 2048) * 5);
                        points[index * 3 + 2] = ((zCode - 32) * 5);
                        
                        if (ctx.withIntensities) {
                            intensities[index] = data[step * i + 4];
                        }
                        index++;
                    }
                });

                let timestamp = new AppoxTimestamp(ctx.session, ctx.offset, ctx.sync, ctx.timeInfo);
                let sample = new AppoxPointCloudSample(timestamp, null, slice.channel, pointCount, ctx.sourcePointCount, points, intensities);
                this.outputStream.push(sample);
            }

            ctx.session = slice.session;
            ctx.offset = slice.offset;
            ctx.sourcePointCount = slice.sourcePointCount;
            ctx.sync = slice.sync;
            ctx.timeInfo = slice.timeInfo;
            ctx.withIntensities = slice.withIntensities;
            ctx.data = [slice.data];
        }
    }
    
    this.dequeue = () => {
        let output = this.outputStream;
        this.outputStream = [];
        return output;
    }
}

function AppoxI7JpegSample(timestamp, timeline, channel, jpeg) {
    this.timestamp = timestamp;
    this.timeline = timeline;
    this.channel = channel;
    this.jpeg = jpeg; // Uint8Array
}

function AppoxI7SecondMosaicPack(channel, session, second, mosaicHeight, vgaHeight, offsets, mosaics) {
    this.channel = channel;
    this.session = session;
    this.second = second;
    this.mosaicHeight = mosaicHeight;
    this.vgaHeight = vgaHeight;
    this.offsets = offsets;
    this.mosaics = mosaics;
}

function AppoxI7MosaicSample(timestamp, timeline, mosaic, vgaHeight) {
    
    this.timestamp = timestamp;
    this.timeline = timeline;
    this.mosaic = mosaic;
    this.vgaHeight = vgaHeight;
    this.jpeg = null;

    this.getJpeg = () => {
        if (this.jpeg == null) {

            let targetWidth = 640;
            let targetHeight = this.vgaHeight;
            let canvas = document.createElement('canvas');
            canvas.width = targetWidth;
            canvas.height = targetHeight;
            let ctx = canvas.getContext('2d');

            let src = this.mosaic.data;
            let srcWidth = this.mosaic.width;
            let srcHeight = this.mosaic.height;
            let rowBytes = this.mosaic.rowBytes;
            let srcImageData = ctx.createImageData(srcWidth, srcHeight);
            let dst = srcImageData.data;
            for (let y = 0; y < srcHeight; y++) {
                for (let x = 0; x < srcWidth; x++) {
                    let srcIdx = y * rowBytes + x * 3;
                    let dstIdx = (y * srcWidth + x) * 4;
                    dst[dstIdx] = src[srcIdx + 2];
                    dst[dstIdx + 1] = src[srcIdx + 1];
                    dst[dstIdx + 2] = src[srcIdx + 0];
                    dst[dstIdx + 3] = 255;
                }
            }
            ctx.putImageData(srcImageData, 0, 0);

            let scaleCanvas = document.createElement('canvas');
            scaleCanvas.width = targetWidth;
            scaleCanvas.height = targetHeight;
            let scaleCtx = scaleCanvas.getContext('2d');
            scaleCtx.imageSmoothingEnabled = false;
            scaleCtx.drawImage(canvas, 0, 0, srcWidth, srcHeight, 0, 0, targetWidth, targetHeight);

            let dataURL = scaleCanvas.toDataURL('image/jpeg');
            let byteString = atob(dataURL.split(',')[1]);
            let buffer = new Uint8Array(byteString.length);
            for (let i = 0; i < byteString.length; i++) {
                buffer[i] = byteString.charCodeAt(i);
            }
            
            this.jpeg = buffer;
        }
        return this.jpeg;
    }
}

function AppoxI7VideoBufferTimelineContext() {
    this.lastGetTime = null;
    this.firstGetTime = null;
    this.getTimeline = 0;
    this.queried = false;
}

function AppoxI7VideoBuffer() {

    this.vgaBuffers = new Array(24);
    this.mosaicBuffers = new Array(24);
    this.timelineCtxs = new Array(24);
    this.transferFlags = new Array(24);
    this.camInfos = new Array(24);
    this.lastQueryInfoIndex = -1;
    this.clipBuffersInterval = 0;
    this.queryInfoInterval = 0;
    this.VideoTransferTimeout = 300;

    this.reset = () => {
        for (let i = 0; i < 24; i++) {
            this.vgaBuffers[i] = [];
            this.mosaicBuffers[i] = [];
            this.timelineCtxs[i] = new AppoxI7VideoBufferTimelineContext();
            this.transferFlags[i] = false;
            this.camInfos[i] = null;
        }
    }

    this.reset();

    this.update = () => {
        let now = Date.now();

        for (let i = 0; i < 24; i++) {
            let shouldTransfer = (now - this.timelineCtxs[i].lastGetTime) < this.VideoTransferTimeout;
            if (shouldTransfer != this.transferFlags[i]) {
                this.transferFlags[i] = shouldTransfer;
                let packet = appoxi7.generateSetVideoTransferPacket(i, shouldTransfer);
                let rawPacket = appoxi7.generateRawPacket(packet);
                if (appoxi7.isConnected()) appoxi7.socket.send(rawPacket);
            }
        }

        if (now - this.queryInfoInterval >= 200) {
            this.queryInfoInterval = now;
            for (let i = 0; i < 24; i++) {
                let channel = (this.lastQueryInfoIndex + 1 + i) % 24;
                if (this.transferFlags[channel] && this.camInfos[channel] == null) {
                    this.queryCamInfo(channel);
                    this.lastQueryInfoIndex = channel;
                    break;
                }
            }
        }

        if (now - this.clipBuffersInterval >= 220) {
            this.clipBuffersInterval = now;
            for (let i = 0; i < 24; i++) {
                appoxUtility.clipSampleBuffer(this.vgaBuffers[i]);
                appoxUtility.clipSampleBuffer(this.mosaicBuffers[i]);
            }
        }
    }

    this.updateWithJpegSample = (sample) => {

        if (sample.channel < 0 || sample.channel >= 24) return;

        let targetBuffer = this.vgaBuffers[sample.channel];
        if (targetBuffer.length == 0 || sample.timeline > targetBuffer[targetBuffer.length - 1].timeline) {
            targetBuffer.push(sample);
            appoxUtility.clipSampleBuffer(targetBuffer);
        }
    }

    this.updateWithSecondMosaicPack = (pack) => {

        if (pack.channel < 0 || pack.channel >= 24) return;
        if (pack.offsets.length == 0 || pack.mosaics.length == 0 || pack.offsets.length != pack.mosaics.length) return;

        let sessionTimeline = appoxUtility.getSessionTimelineLocal(pack.session);
        if (sessionTimeline == null) return;

        let targetBuffer = this.mosaicBuffers[pack.channel];
        for (let i = 0; i < pack.offsets.length; i++) {
            let offset = pack.offsets[i];
            let mosaic = pack.mosaics[i];
            let sample = new AppoxI7MosaicSample(new AppoxTimestamp(pack.session, offset, appoxEnums.TimeOffsetSync.HostArrival, null), sessionTimeline + offset, mosaic, pack.vgaHeight);
            if (targetBuffer.length == 0 || sample.timeline > targetBuffer[targetBuffer.length - 1].timeline) {
                targetBuffer.push(sample);
            }
        }
        appoxUtility.clipSampleBuffer(targetBuffer);
    }

    this.getPreviewJpeg = (channel, timeline, maxGap) => {
        if (channel < 0 || channel >= 24) return [null, null, null];

        let now = Date.now();
        this.timelineCtxs[channel].lastGetTime = now;
        if (timeline != this.timelineCtxs[channel].getTimeline) {
            this.timelineCtxs[channel].getTimeline = timeline;
            this.timelineCtxs[channel].firstGetTime = now;
            this.timelineCtxs[channel].queried = false;
        }

        let vgaSample = null;
        let vgaResult = appoxUtility.searchSample(this.vgaBuffers[channel], timeline, maxGap);
        if (vgaResult != null) vgaSample = vgaResult.weight1 >= vgaResult.weight2 ? vgaResult.sample1 : vgaResult.sample2;

        let mosaicSample = null;
        let mosaicResult = appoxUtility.searchSample(this.mosaicBuffers[channel], timeline, maxGap);
        if (mosaicResult != null) mosaicSample = mosaicResult.weight1 >= mosaicResult.weight2 ? mosaicResult.sample1 : mosaicResult.sample2;

        if (vgaSample == null) {
            this.queryPreviewJpegIfNecessary(channel, timeline);
            if (mosaicSample == null) return [null, null, null];
            else return [mosaicSample.getJpeg(), mosaicSample.timestamp, null];
        }
        else {
            if (mosaicSample == null) return [vgaSample.jpeg, vgaSample.timestamp, this.camInfos[channel]];
            else {
                let dtVga = Math.abs(vgaSample.timeline - timeline);
                let dtMosaic = Math.abs(mosaicSample.timeline - timeline) + 0.001;
                if (dtVga <= dtMosaic) return [vgaSample.jpeg, vgaSample.timestamp, this.camInfos[channel]];
                else {
                    this.queryPreviewJpegIfNecessary(channel, timeline);
                    return [mosaicSample.getJpeg(), mosaicSample.timestamp, null];
                }
            }
        }
    }

    this.queryCamInfo = async (channel) => {
        let timeline = -1;
        if (this.vgaBuffers[channel].length > 0) {
            timeline = this.vgaBuffers[channel][this.vgaBuffers[channel].length - 1].timeline;
        }
        if (timeline < 0 && this.mosaicBuffers[channel].length > 0) {
            timeline = this.mosaicBuffers[channel][this.mosaicBuffers[channel].length - 1].timeline;
        }
        if (timeline < 0) return;
        
        let result = await agencyAsync.getPreviewJpeg(channel, timeline, 1);
        let camInfo = result[2];
        if (camInfo == null) return;

        if (camInfo.metaProtocol == "generic-v1" || camInfo.metaProtocol == "generic-v2") {
            camInfo.meta[22] = camInfo.meta[23] = 0;
        }
        else if (camInfo.metaProtocol == "fisheye-v1") {
            camInfo.meta[24] = camInfo.meta[25] = 0;
        }

        this.camInfos[channel] = camInfo;
    }

    this.queryPreviewJpegIfNecessary = async (channel, targetTimeline) => {
        let now = Date.now();
        if (this.timelineCtxs[channel].queried || (now - this.timelineCtxs[channel].firstGetTime) < 200) return;

        let result = await agencyAsync.getPreviewJpeg(channel, targetTimeline, 1);
        let jpeg = result[0];
        let timestamp = result[1];
        if (jpeg == null || timestamp == null) return;

        let sessionTimeline = await agencyAsync.getSessionTimeline(timestamp.session);
        if (sessionTimeline == null) return;

        let actualTimeline = sessionTimeline + timestamp.offset;
        let sample = new AppoxI7JpegSample(timestamp, actualTimeline, channel, jpeg);
        let targetBuffer = this.vgaBuffers[channel];

        if (targetBuffer.length == 0) {
            targetBuffer.push(sample);
        }
        else if (actualTimeline < targetBuffer[0].timeline) {
            targetBuffer.unshift(sample);
        }
        else if (actualTimeline > targetBuffer[targetBuffer.length - 1].timeline) {
            targetBuffer.push(sample);
        }
        else {
            let searchResult = appoxUtility.searchSample(targetBuffer, actualTimeline, Number.POSITIVE_INFINITY);
            if (searchResult != null && actualTimeline > searchResult.sample1.timeline && actualTimeline < searchResult.sample2.timeline) {
                targetBuffer.splice(targetBuffer.indexOf(searchResult.sample2), 0, sample);
            }
        }
    }
}

function AppoxI7TimelineManager() {

    this.hostInterest = 0;
    this.hostBufferRange = new AppoxBufferRange(0, 0);
    this.hostTask = new AppoxTaskBeat();
    this.smallestSampleTimeline = 0;
    this.latestSampleTimeline = 0;
    this.targetTimelineGap = 0.5;
    this.actualTimelineGap = 0.5;
    this.updateGapInterval = 0;

    this.RedundantGap = 0.1;
    this.TuneCoef = 0.1;
    this.DecayCoef = 0.01;

    this.resetByStartSession = () => {
        this.smallestSampleTimeline = 0;
        this.latestSampleTimeline = 0;
        this.targetTimelineGap = this.actualTimelineGap = 0.5;
    }

    this.resetByConnected = () => {
        this.hostInterest = 0;
        this.hostBufferRange = new AppoxBufferRange(0, 0);
    }

    this.updateBufferRangeAndInterest = () => {
        this.hostTask.handle(async () => {
            await Promise.all([this.queryInterest(), this.queryBufferRange()]);
        });
    }

    this.updateWithSampleRange = (lower, upper) => {

        this.latestSampleTimeline = Math.max(this.latestSampleTimeline, upper);

        let increasedTargetTimelineGap = false;
        if (lower > this.latestSampleTimeline - 3.0) {
            this.smallestSampleTimeline = Math.min(this.smallestSampleTimeline, lower);
            let curTimelineGap = this.latestSampleTimeline - lower + this.RedundantGap;
            if (curTimelineGap > this.targetTimelineGap) {
                this.targetTimelineGap = curTimelineGap;
                increasedTargetTimelineGap = true;
            }
        }

        let now = Date.now();
        if (now - this.updateGapInterval >= 100) {
            this.updateGapInterval = now;
            if (!increasedTargetTimelineGap) this.targetTimelineGap -= this.DecayCoef;
            this.actualTimelineGap = this.actualTimelineGap * (1.0 - this.TuneCoef) + this.targetTimelineGap * this.TuneCoef;
        }
    }

    this.getBufferRange = () => {
        let lower = Math.max(this.hostBufferRange.begin, this.smallestSampleTimeline);
        let upper = Math.min(this.hostBufferRange.end, this.latestSampleTimeline);
        if (lower >= upper) return new AppoxBufferRange(0, 0);
        else return new AppoxBufferRange(lower, upper);
    }

    this.getInterest = () => {
        let lower = Math.max(this.hostBufferRange.begin, this.smallestSampleTimeline);
        let upper = Math.min(this.hostBufferRange.end, this.latestSampleTimeline - this.actualTimelineGap);
        if (lower >= upper) return lower;
        else return Math.max(Math.min(this.hostInterest, upper), lower);
    }

    this.queryInterest = async () => {
        this.hostInterest = await agencyAsync.getInterestTime();
    }

    this.queryBufferRange = async () => {
        this.hostBufferRange = await agencyAsync.getBufferRange() || new AppoxBufferRange(0, 0);
    }
}

function AppoxI7SecondProfileManager() {
    this.enable = true;
    this.sessions = new Map(); // sessionIdString -> SessionProfileContext
    this.outputs = [];
    this.busProtocols = [];
    for (let i = 0; i < 12; i++) this.busProtocols[i] = "bus@" + (i + 1);

    this.setEnable = (enable) => {
        this.enable = enable;
    };

    this.reset = () => {
        this.sessions.clear();
        this.outputs = [];
    };

    this.dequeue = () => {
        let ret = this.outputs;
        this.outputs = [];
        return ret;
    };

    this.onSecondProfileProto = (proto) => {
        if (!this.enable) return;
        let session = new AppoxSessionIdentifier(proto.Session);
        if (!session.isValid) return;
        let sessionTimeline = appoxUtility.getSessionTimelineLocal(session);
        if (sessionTimeline == null) return;
        let sessionCtx = this.sessions.get(session.toString());
        if (!sessionCtx) {
            sessionCtx = new AppoxI7SessionProfileContext();
            this.sessions.set(session.toString(), sessionCtx);
        }
        let secondCtx = sessionCtx.pop(proto.Second);
        if (!secondCtx) secondCtx = new AppoxI7SecondProfileContext(proto.Second);
        for (let channel of proto.Channels) {
            let channelCtx = secondCtx.queryChannelContext(channel.ID);
            channelCtx.setSourceRemote(channel.SourceCount, channel.Offsets);
        }
        this.outputs.push(secondCtx.toPack(session, sessionTimeline));
    };

    this.onAvailableBusData = (sample) => {
        if (!this.enable) return;
        let ch = sample.channel - 1;
        if (ch >= this.busProtocols.length) return;
        let sessionCtx = this.sessions.get(sample.timestamp.session.toString());
        if (!sessionCtx) {
            sessionCtx = new AppoxI7SessionProfileContext();
            this.sessions.set(sample.timestamp.session.toString(), sessionCtx);
        }
        let secondCtx = sessionCtx.querySecond(Math.floor(sample.timestamp.offset));
        if (!secondCtx) return;
        let channelCtx = secondCtx.queryChannelContext(this.busProtocols[ch]);
        channelCtx.addAvailableSample();
    };

    this.onAvailableVideoData = (sample) => {
        if (!this.enable) return;
        let sessionCtx = this.sessions.get(sample.timestamp.session.toString());
        if (!sessionCtx) {
            sessionCtx = new AppoxI7SessionProfileContext();
            this.sessions.set(sample.timestamp.session.toString(), sessionCtx);
        }
        let secondCtx = sessionCtx.querySecond(Math.floor(sample.timestamp.offset));
        if (!secondCtx) return;
        let channelCtx = secondCtx.queryChannelContext("video@" + sample.channel);
        channelCtx.addAvailableSample();
    };

    this.onAvailableAudioData = (sample) => {
        if (!this.enable) return;
        let sessionCtx = this.sessions.get(sample.timestamp.session.toString());
        if (!sessionCtx) {
            sessionCtx = new AppoxI7SessionProfileContext();
            this.sessions.set(sample.timestamp.session.toString(), sessionCtx);
        }
        let secondCtx = sessionCtx.querySecond(Math.floor(sample.timestamp.offset));
        if (!secondCtx) return;
        let channelCtx = secondCtx.queryChannelContext("audio");
        channelCtx.addAvailableSample();
    };

    this.onAvailableGeneralSample = (sample) => {
        if (!this.enable) return;
        let sessionCtx = this.sessions.get(sample.timestamp.session.toString());
        if (!sessionCtx) {
            sessionCtx = new AppoxI7SessionProfileContext();
            this.sessions.set(sample.timestamp.session.toString(), sessionCtx);
        }
        let secondCtx = sessionCtx.querySecond(Math.floor(sample.timestamp.offset));
        if (!secondCtx) return;
        let protocol = sample.protocol + (sample.channel == null ? "" : ("@" + sample.channel));
        let channelCtx = secondCtx.queryChannelContext(protocol);
        channelCtx.addAvailableSample();
    };

    this.onAvailablePointCloudSample = (sample) => {
        if (!this.enable) return;
        let sessionCtx = this.sessions.get(sample.timestamp.session.toString());
        if (!sessionCtx) {
            sessionCtx = new AppoxI7SessionProfileContext();
            this.sessions.set(sample.timestamp.session.toString(), sessionCtx);
        }
        let secondCtx = sessionCtx.querySecond(Math.floor(sample.timestamp.offset));
        if (!secondCtx) return;
        let protocol = "point-cloud@" + sample.channel;
        let channelCtx = secondCtx.queryChannelContext(protocol);
        channelCtx.addAvailableSample();
    };
}

function AppoxI7ChannelProfileContext() {
    this.nativeSourceCount = 0;
    this.appSourceCount = 0;
    this.remoteSourceCount = 0;
    this.offsets = [];
    this.availableCount = 0;

    this.addSourceAppSample = (offset) => {
        this.appSourceCount++;
        this.offsets.push(offset);
    };
    this.setSourceNative = (sourceCount, offsets) => {
        this.nativeSourceCount = sourceCount;
        this.offsets = this.offsets.concat(offsets);
    };
    this.setSourceRemote = (sourceCount, offsets) => {
        this.remoteSourceCount = sourceCount;
        this.offsets = this.offsets.concat(offsets);
    };
    this.addAvailableSample = () => {
        this.availableCount++;
    };
    this.getSourceCount = () => {
        return this.nativeSourceCount + this.appSourceCount + this.remoteSourceCount;
    };
    this.querySortedOffsets = () => {
        return this.offsets.slice().sort((a, b) => a - b);
    };
}

function AppoxI7SecondProfileContext(second) {
    this.second = second;
    this.channels = new Map(); // protocol -> AppoxI7ChannelProfileContext
    this.queryChannelContext = (protocol) => {
        if (!this.channels.has(protocol)) {
            this.channels.set(protocol, new AppoxI7ChannelProfileContext());
        }
        return this.channels.get(protocol);
    };
    this.toPack = (session, sessionTimeline) => {
        let profiles = {};
        for (let [protocol, ctx] of this.channels.entries()) {
            profiles[protocol] = new AppoxSecondProfileSample(
                new AppoxTimestamp(session, this.second + 0.5, appoxEnums.TimeOffsetSync.Interpolated, null),
                sessionTimeline + this.second + 0.5, protocol, this.second, ctx.getSourceCount(), ctx.availableCount, ctx.querySortedOffsets());
        }
        return new AppoxSecondProfilePack(session, this.second, profiles);
    };
}

function AppoxI7SessionProfileContext() {
    this.seconds = [];
    this.channelIDFlags = {};
    this.lastOutputSecond = -1;
    this.querySecond = (second) => {
        if (second <= this.lastOutputSecond) return null;
        if (this.seconds.length > 0) {
            if (second > this.seconds[this.seconds.length - 1].second) {
                if (second > this.seconds[this.seconds.length - 1].second + 3) return null;
                for (let newSecond = this.seconds[this.seconds.length - 1].second + 1; newSecond <= second; newSecond++) {
                    let ctx = new AppoxI7SecondProfileContext(newSecond);
                    this.seconds.push(ctx);
                    if (newSecond === second) return ctx;
                }
                return null;
            } else if (second < this.seconds[0].second) {
                if (second < this.seconds[0].second - 1) return null;
                for (let newSecond = this.seconds[0].second - 1; newSecond >= second; newSecond--) {
                    let ctx = new AppoxI7SecondProfileContext(newSecond);
                    this.seconds.unshift(ctx);
                    if (newSecond === second) return ctx;
                }
                return null;
            } else {
                let index = second - this.seconds[0].second;
                return this.seconds[index];
            }
        } else {
            let ctx = new AppoxI7SecondProfileContext(second);
            this.seconds.push(ctx);
            return ctx;
        }
    };
    this.dequeue = (mtSecond) => {
        let channelIDs = Object.keys(this.channelIDFlags);
        let outputs = [];
        let minSecond = mtSecond - 6 + 1;
        let count = 0;
        for (let ctx of this.seconds) {
            if (ctx.second < minSecond) {
                for (let id of channelIDs) ctx.queryChannelContext(id);
                outputs.push(ctx);
                count++;
            } else break;
        }
        this.seconds.splice(0, count);
        return outputs;
    };
    this.pop = (second) => {
        let channelIDs = Object.keys(this.channelIDFlags);
        let buffer = [];
        let output = null;
        for (let ctx of this.seconds) {
            if (ctx.second < second + 1) {
                for (let id of channelIDs) ctx.queryChannelContext(id);
                buffer.push(ctx);
                if (ctx.second === second) output = ctx;
            } else break;
        }
        this.seconds.splice(0, buffer.length);
        return output;
    };
    this.touchChannel = (channelID) => {
        this.channelIDFlags[channelID] = true;
    };
}

function AppoxI7RoundTripTimeManager() {

    this.lastQueryTime = Date.now();
    this.queryTask = new AppoxTaskBeat();

    this.update = () => {

        let now = Date.now();
        if (now - this.lastQueryTime < 200) return;
        this.lastQueryTime = now;
        
        this.queryTask.handle(async () => {
            let t0 = Date.now();
            await agencyAsync.getAppStatus();
            let time = Math.floor(Date.now() - t0);
            if (time > 1) {
                let packet = appoxi7.generateRoundTripTimePacket(time);
                let rawPacket = appoxi7.generateRawPacket(packet);
                if (appoxi7.isConnected()) appoxi7.socket.send(rawPacket);
            }
        });
    }
}

function AppoxI7CompatibleRandom(seed) {

    this.inext = 0;
    this.inextp = 21;
    this.seedArray = new Array(56);

    let num = (seed === -2147483648) ? 2147483647 : Math.abs(seed);
    this.seedArray[55] = num;
    let num3 = 1;
    for (let i = 1; i < 55; i++) {
        let num4 = (21 * i) % 55;
        this.seedArray[num4] = num3;
        num3 = num - num3;
        if (num3 < 0) {
            num3 += 2147483647;
        }
        num = this.seedArray[num4];
    }
    for (let j = 1; j < 5; j++) {
        for (let k = 1; k < 56; k++) {
            this.seedArray[k] -= this.seedArray[1 + (k + 30) % 55];
            if (this.seedArray[k] < 0) {
                this.seedArray[k] += 2147483647;
            }
        }
    }
    
    this.next = () => {
        let num = this.inext;
        let num2 = this.inextp;
        if (++num >= 56) num = 1;
        if (++num2 >= 56) num2 = 1;
        let num3 = this.seedArray[num] - this.seedArray[num2];
        if (num3 === 2147483647) num3 = 2147483646;
        if (num3 < 0) num3 += 2147483647;
        this.seedArray[num] = num3;
        this.inext = num;
        this.inextp = num2;
        return num3;
    }
    
    this.nextBytes = (buffer) => {
        for (let i = 0; i < buffer.length; i++) {
            buffer[i] = this.next() & 0xFF;
        }
    }
}

function AppoxI7Masks() {
    
    this.masks = new Array(256);
    this.seedUpdated = false;

    let random = new AppoxI7CompatibleRandom(appoxi7.PacketMagicHead[appoxi7.PacketMagicHead.length - 1]);

    for (let i = 0; i < 256; i++) {
        this.masks[i] = new Uint8Array(2048);
        if (i === 255) {
            random.nextBytes(this.masks[i]);
        }
    }
    
    this.updateSeed = (maskSeed) => {
        if (this.seedUpdated) return;
        let random = new AppoxI7CompatibleRandom(maskSeed);
        for (let i = 0; i < 255; i++) {
            random.nextBytes(this.masks[i]);
        }
        this.seedUpdated = true;
    };
    
    this.addMask = (target, maskID) => {
        if (!this.seedUpdated) maskID = 255;
        let mask = this.masks[maskID];
        let maskIndex = 0;
        for (let i = 0; i < target.length; i++) {
            target[i] = target[i] ^ mask[maskIndex++];
            if (maskIndex === mask.length) maskIndex = 0;
        }
        return maskID;
    };
    
    this.removeMask = (target, maskID) => {
        if (!this.seedUpdated && maskID !== 255) return false;
        let mask = this.masks[maskID];
        let maskIndex = 0;
        for (let i = 0; i < target.length; i++) {
            target[i] = target[i] ^ mask[maskIndex++];
            if (maskIndex === mask.length) maskIndex = 0;
        }
        return true;
    };
}